IEqualityComparer<in T>
is an interface that will handle equality comparisons for your collection. Your collection will delegate equiality comparisons to this interface. You may ask, why don't just call the Equals
method?
Because there can be several kinds of possible comparisons. Let's take an easy example: are "Abc"
and "ABC"
equal? It depends. "Abc".Equals("ABC") == false
but what if you want case insensitivity?
This is why your collection should delegate the equality comparisons to a different class. By composing classes, you'll respect the single responsibility principle: Your collection knows how to store the items, and the equality comparer knows if they're equal.
An example with sets:
var caseSensitive = new HashSet<string>(StringComparer.Ordinal) // The default anyway
{
"Abc", "ABC"
};
var caseInsensitive = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"Abc", "ABC"
};
The results will be:
caseSensitive.Count == 2
caseInsensitive.Count == 1
caseSensitive.Contains("aBc") == false
caseInsensitive.Contains("aBc") == true
Here you have two completely different set semantics using the same HashSet
class.
Now, what's in an IEqualityComparer<in T>
?
bool Equals(T x, T y);
: This method does just what you'd expect it to: it returns true
if x
should be considered equal to y
. Just like the mathematical equality, it must be:
- reflexive:
Equals(x, x) == true
- symmetric:
Equals(x, y) == Equals(y, x)
- transitive: if
Equals(x, y) && Equals(y, z)
then Equals(x, z)
int GetHashCode(T obj);
this one may be trickier to get right. For each obj
, it should return a hash code wilth the following properties:
- it should never change
- if
Equals(x, y)
then GetHashCode(x) == GetHashCode(y)
- there should be as few collisions as possible
Note that this does not imply that if GetHashCode(x) == GetHashCode(y)
then Equals(x, y)
. Two objects can have the same hash code but be inequal (there can be at most 0xFFFFFFFF
possible hash codes after all).
Collections often use the hash code to organize their items. For example, the HashSet
will know that if two objects don't have the same hash code, they won't be equal and thus can organize its buckets accordingly. Hash codes are just an optimization.
Now, what's EqualityComparer<T>.Default
? It's a conventient shortcut for an IEqualityComparer<T>
that will use an object's own Equals
and GetHashCode
functions. This is a good default value as this is what you want to do most of the time: while strings can have multiple natural comparison types, this is not the case for integers for instance.
EqualityComparer<T>.Default
will handle a couple special cases:
- if
T is IEquatable<T>
, it will use the IEquatable<T>
interface
- if
T is Nullable<U>
and U is IEquatable<U>
it will handle the case properly
- it will optimize for a couple special cases:
byte[]
and int-basd Enum
.
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…