Using a class versus struct as a dictionary key

Kyle Baran picture Kyle Baran · May 10, 2013 · Viewed 29.3k times · Source

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.

Answer

Sam Harwell picture Sam Harwell · May 10, 2013

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 overrides Object.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, the Equals method performs a byte-by-byte comparison of the two objects in memory. Otherwise, it uses reflection to compare the corresponding fields of obj 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.