When (not) to override Equals?

In .NET, you can override operators as well as the default implementation of the Equals method. While this looks like a nice feature (it is if you know what you're doing), you should be very careful because it can have unexpected repercussions. First, read this. Then this.
One unexpected effect of overriding Equals is that if you do it, you should also override GetHashCode, if only because the Hashtable implementation relies on both being in sync for the objects used as the keys.
Your implementation should respect three rules:
  1. Two objects for which Equals returns true should have the same hash code.
  2. The hashcode distribution for instances of a class should be random.
  3. If you get a hash code for an object and modify the object's properties, the hash code should remain the same (just as the song).
While the first requirement ensures consistency if your class instances are used as the key in a hashtable, the second ensures good performance of the hashtable.
The third requirement has an annoying consequence: the properties that you use to compute the hash must be immutable (ie, they must be set from a constructor only and be impossible to set at any time after that).
So what should you do if your Equals implementation involves mutable properties? Well, you could exclude these from the computation of the hash and only take into account the immutable ones, but doing so, you're destroying requirement number 2.
The answer is that you should actually never override Equals on a mutable type. You should instead create a ContentsEquals (or whatever name you may choose) method to compare the instances and leave Equals do its default reference comparison. Don't touch GetHashCode in this case.
 
Update: It may seem reasonable to say that it's ok to override Equals and GetHashCode on mutable objects if you document clearly that once the object has been used as the key in a hashtable, it should not be changed and that if it is, unpredictable things can happen. The problem with that, though, is that it's not easily discoverable (documentation only). Thus, it is better to avoid overriding them altogether on mutable objects.

5 Comments

  • Sigurdur, that's a very good question. For value types, well, they should really be immutable anyway, but for reference types that must be IComparable, in short, for the moment, I don't know exactly. I've asked the question to some CLR gurus and I'm waiting for an answer. Stay tuned.

    What I can recommend for the moment is that you base your implementations of IComparable, Equals and GetHashCode on immutable properties if you can (even if the rest of the type is not immutable).

    As a last resort solution, if you don't need to use these types as keys in a hash table (which is the main reason to use the hash), you could throw from GetHashCode.

  • OK, so the gurus agree that IComparable should really only be applied to immutable objects. So if you implement it on mutable objects, you must be breaking something. What you're breaking depends on what requirement you sacrificed in your implementation of Equals and GetHashCode. The documentation may be updated in the future to reflect that.

    One other thing you could do is provide a method that enables the locking of the objects before you use them as keys in a collection object where the hash and Equals must be in sync (for example Hashtable), but that is a half measure as the users of your class must know about this problem and agree to lock the objects before using them as keys. Throwing from GetHashCode if the object is not locked could help prevent the problem.

    Now, doing that is not ideal as it kind of breaks the pattern whatever you do: GetHashCode is implemented in object and it's not supposed to throw.

    So the advice is "don't" and expect unexpected consequences if you do.

  • This is one of the most useful pages I've found on overriding equals and gethashcode. It clears up so much, and applies equally to Java too! Thanks!

  • I second Ryan's comment - you've cleared a gathering headache, thanks.

  • Ajoka: those rules really apply to value types and similar entities. All your examples are pure reference types for which equality and hash codes are associated to the reference, not to the state, and should not be overridden under normal circumstances.

Comments have been disabled for this content.