How .NET Dictionary implementation works with mutable objects
It doesn't. The documentation for Dictionary states:
As long as an object is used as a key in the Dictionary<TKey, TValue>
, it must not change in any way that affects its hash value.
Since you're changing the object while it's in the Dictionary
it will not work.
As for why, it's not too hard to see. We put in an object. Let's assume that the hash code is 1
. We put the object in the 1
bucket of our hash table. Now the object is mutated from outside the Dictionary so that it's value (and hash code) is 2
. Now when someone gives that object to the dictionary's indexer it gets the hash code, see's that it's 2
, and looks in the 2
bucket. That bucket is empty, so it says, "sorry, no element".
Now let's assume that a new object is created with a value and hash of 1
. It's passed to the Dictionary, who sees that the hash is 1
. It looks in the 1
bucket and finds that there is indeed an item at that index. It now uses Equals
to determine if the objects are in fact equal (or if this is just a hash collision).
Now, in your case, it will fail here because you don't override Equals
, you're using the default implementation which compares references, and since this is a different object it won't have the same reference. However, even if you changed it to compare the values, *the first object was mutated to have a value of 2
, not 1
, so it won't match anyway. Others have suggested fixing this Equals
method, and you really should do that, but it still won't fix your problem.
Once the object is mutated the only way of finding it is if it just so happens that the mutated value is a hash collision (which is possible, but unlikely). If it's not, then anything that is equal according to Equals
will never know to check the right bucket, and anything that checks the right bucket won't be equal according to Equals
.
The quote I mentioned at the start isn't just a best practice. It's not just unexpected or weird or unperformant to mutate items in a dictionary. It just doesn't work.
Now, if the object is mutable but isn't mutated while it's in the dictionary then that's fine. It may be a bit odd, and that's a case people may say is bad practice, even if it works.