Suppose I had the following class and structure definition, and used them each as a key in a dictionary object:
public class MyClass { }
public struct MyStruct { }
public Dictionary<MyClass, string> ClassDictionary;
public Dictionary<MyStruct, string> StructDictionary;
ClassDictionary = new Dictionary<MyClass, string>();
StructDictionary = new Dictionary<MyStruct, string>();
Why is it that this works:
MyClass classA = new MyClass();
MyClass classB = new MyClass();
this.ClassDictionary.Add(classA, "Test");
this.ClassDictionary.Add(classB, "Test");
But this crashes on runtime:
MyStruct structA = new MyStruct();
MyStruct structB = new MyStruct();
this.StructDictionary.Add(structA, "Test");
this.StructDictionary.Add(structB, "Test");
It says the key already exists, as expected, but only for the struct. The class treats it as two separate entries. I think it has something to do with the data being held as a reference versus value, but I would like a more detailed explanation as to why.
Dictionary<TKey, TValue>
uses an IEqualityComparer<TKey>
for comparing the keys. If you do not explicitly specify the comparer when you construct the dictionary, it will use EqualityComparer<TKey>.Default
.
Since neither MyClass
nor MyStruct
implement IEquatable<T>
, the default equality comparer will call Object.Equals
and Object.GetHashCode
for comparing instances. MyClass
is derived from Object
, so the implementation will use reference equality for comparison. MyStruct
on the other hand is derived from System.ValueType
(the base class of all structs), so it will use ValueType.Equals
for comparing the instances. The documentation for this method states the following:
The
ValueType.Equals(Object)
method overridesObject.Equals(Object)
and provides the default implementation of value equality for all value types in the .NET Framework.If none of the fields of the current instance and
obj
are reference types, theEquals
method performs a byte-by-byte comparison of the two objects in memory. Otherwise, it uses reflection to compare the corresponding fields ofobj
and this instance.
The exception occurs because IDictionary<TKey, TValue>.Add
throws an ArgumentException
if "An element with the same key already exists in the [dictionary]." When using structs, the byte-by-byte comparison done by ValueType.Equals
results in both calls attempting to add the same key.