Well, there are pros and cons to any implementation of GetHashCode()
. These are of course the things we weigh up when implementing our own, but in the case of ValueType.GetHashCode()
there is a particular difficulty in that they don't have much information on what the actual details of the concrete type will be. Of course, this often happens to us when we are creating an abstract class or one intended to be the base of classes that will add a lot more in terms of state, but in those cases we have an obvious solution of just using the default implementation of object.GetHashCode()
unless a derived class cares to override it there.
With ValueType.GetHashCode()
they don't have this luxury as the primary difference between a value type and a reference type is, despite the popularity of talking about implementation details of stack vs. heap, the fact that for a value type equivalence relates to value while for an object type equivalence relates to identity (even when an object defines a different form of equivalence by overriding Equals()
and GetHashCode()
the concept of reference-equality still exists and is still useful.
So, for the Equals()
method the implementation is obvious; check the two objects are the same type, and if it is then check also that all fields are equal (actually there's an optimisation which does a bitwise comparison in some cases, but that's an optimisation on the same basic idea).
What to do for GetHashCode()
? There simply is no perfect solution. One thing they could do is some sort of mult-then-add or shift-then-xor on every field. That would likely give a pretty good hashcode, but could be expensive if there were a lot of fields (never mind that its not recommended to have value-types that have lots of fields, the implementer has to consider that they still can, and indeed there might even be times when it makes sense, though I honestly can't imagine a time where it both makes sense and it also makes sense to hash it). If they knew some fields were rarely different between instances they could ignore those fields and still have a pretty good hashcode, while also being quite fast. Finally, they can ignore most fields, and hope that the one(s) they don't ignore vary in value most of the time. They went for the most extreme version of the latter.
(The matter of what is done when there is no instance fields is another matter and a pretty good choice, such value types are equal to all other instances of the same type, and they've a hashcode that matches that).
So, it's an implementation that sucks if you are hashing lots of values where the first field is the same (or otherwise returns the same hashcode), but other implementations would suck in other cases (Mono goes for xoring all the fields' hashcodes together, better in your case, worse in others).
The matter of changing field order doesn't matter, as hashcode is quite clearly stated as only remaining valid for the lifetime of a process and not being suitable for most cases where they could be persisted beyond that (can be useful in some caching situations where it doesn't hurt if things aren't found correctly after a code change).
So, not great, but nothing would be perfect. It goes to show that one must always consider both sides of what "equality" means when using an object as a key. It's easily fixed in your case with:
public class KVPCmp<TKey, TValue> : IEqualityComparer<KeyValuePair<TKey, TValue>>, IEqualityComparer
{
bool IEqualityComparer.Equals(object x, object y)
{
if(x == null)
return y == null;
if(y == null)
return false;
if(!(x is KeyValuePair<TKey, TValue>) || !(y is KeyValuePair<TKey, TValue>))
throw new ArgumentException("Comparison of KeyValuePairs only.");
return Equals((KeyValuePair<TKey, TValue>) x, (KeyValuePair<TKey, TValue>) y);
}
public bool Equals(KeyValuePair<TKey, TValue> x, KeyValuePair<TKey, TValue> y)
{
return x.Key.Equals(y.Key) && x.Value.Equals(y.Value);
}
public int GetHashCode(KeyValuePair<TKey, TValue> obj)
{
int keyHash = obj.GetHashCode();
return ((keyHash << 16) | (keyHash >> 16)) ^ obj.Value.GetHashCode();
}
public int GetHashCode(object obj)
{
if(obj == null)
return 0;
if(!(obj is KeyValuePair<TKey, TValue>))
throw new ArgumentException();
return GetHashCode((KeyValuePair<TKey, TValue>)obj);
}
}
Use this as your comparator when creating your dictionary, and all should be well (you only need the generic comparator methods really, but leaving the rest in does no harm and can be useful to have sometimes).