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.
Class instances each have an identity and are passed by reference, while structs are handled and mutated as values. Basically, if we want all of the changes that are made to a given object to be applied the same instance, then we should use a class — otherwise a struct will most likely be a more appropriate choice.
Any type that conforms to the Hashable protocol can be used as a dictionary's Key type, including all of Swift's basic types. You can use your own custom types as dictionary keys by making them conform to the Hashable protocol.
Their intent tends to be different, as revealed by their names: structs are for structured collections of data, while dictionaries are for long lists of named elements.
Class is a reference type, whereas Struct is a value type. A default constructor or destructor cannot be created in Struct. Structs inherit from System. ValueType , cannot be inherited from another Struct or Class, and cannot be a base class.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With