Why isn't .Except (LINQ) comparing things properly? (using IEquatable)
After investigation, it turns out things aren't quite as bad as I thought. Basically, when everything is implemented properly (GetHashCode
, etc.) the documentation is correct, and the behavior is correct. But, if you try to do something like implement IEquatable
all by itself, then your Equals
method will never get called (this seems to be due to GetHashCode
not being implemented properly). So, while the documentation is technically wrong, it's only wrong in a fringe situation that you'd never ever want to do (if this investigation has taught me anything, it's that IEquatable
is part of a whole set of methods you should implement atomically (by convention, not by rule, unfortunately)).
Good sources on this are:
- Is there a complete IEquatable implementation reference?
- MSDN Documentation:
IEquatable<T>.Equals(T)
Method - SYSK 158:
IComparable<T>
vs.IEquatable<T>
The interface IEqualityComparer<T>
has these two methods:
bool Equals(T x, T y);
int GetHashCode(T obj);
A good implementation of this interface would thus implement both. The Linq extension method Except relies on the hash code in order to use a dictionary or set lookup internally to figure out which objects to skip, and thus requires that proper GetHashCode implementation.
Unfortunately, when you use EqualityComparer<T>.Default
, that class does not provide a good GetHashCode implementation by itself, and relies on the object in question, the type T, to provide that part, when it detects that the object implements IEquatable<T>
.
The problem here is that IEquatable<T>
does not in fact declare GetHashCode
so it's much easier to forget to implement that method properly, contrasted with the Equals
method that it does declare.
So you have two choices:
- Provide a proper
IEqualityComparer<T>
implementation that implements bothEquals
andGetHashCode
- Make sure that in addition to implementing
IEquatable<T>
on your object, implement a properGetHashCode
as well