I want to understand the scenarios where IEqualityComparer<T>
and IEquatable<T>
should be used.
The MSDN documentation for both looks very similar.
IEqualityComparer<T>
is an interface for an object that performs the comparison on two objects of the type T
.
IEquatable<T>
is for an object of type T
so that it can compare itself to another of the same type.
IEqualityComparer<T>
is an interface for an object (which is usually a lightweight class different from T
) that provides comparison functions that operates on T
–
Vertex When deciding whether to use IEquatable<T>
or IEqualityComparer<T>
,
one could ask:
Is there a preferred way of testing two instances of
T
for equality, or are there several equally valid ways?
If there is only one way of testing two instances of
T
for equality, or if one of several methods is preferred, thenIEquatable<T>
would be the right choice: This interface is supposed to be implemented only byT
itself, so that one instance ofT
has internal knowledge of how to compare itself to another instance ofT
.On the other hand, if there are several equally reasonable methods of comparing two
T
s for equality,IEqualityComparer<T>
would seem more appropriate: This interface is not meant to be implemented byT
itself, but by other "external" classes. Therefore, when testing two instances ofT
for equality, becauseT
has no internal understanding of equality, you will have to make an explicit choice of aIEqualityComparer<T>
instance which performs the test according to your specific requirements.
Example:
Let's consider these two types (which are supposed to have value semantics):
interface IIntPoint : IEquatable<IIntPoint>
{
int X { get; }
int Y { get; }
}
interface IDoublePoint // does not inherit IEquatable<IDoublePoint>; see below.
{
double X { get; }
double Y { get; }
}
Why would only one of these types inherit IEquatable<>
, but not the other?
In theory, there is only one sensible way of comparing two instances of either type: They are equal if the X
and Y
properties in both instances are equal. According to this thinking, both types should implement IEquatable<>
, because it doesn't seem likely that there are other meaningful ways of doing an equality test.
The issue here is that comparing floating-point numbers for equality might not work as expected, due to minute rounding errors. There are different methods of comparing floating-point numbers for near-equality, each with specific advantages and trade-offs, and you might want to be able to choose yourself which method is appropriate.
sealed class DoublePointNearEqualityComparerByTolerance : IEqualityComparer<IDoublePoint>
{
public DoublePointNearEqualityComparerByTolerance(double tolerance) { … }
…
public bool Equals(IDoublePoint a, IDoublePoint b)
{
return Math.Abs(a.X - b.X) <= tolerance && Math.Abs(a.Y - b.Y) <= tolerance;
}
…
}
Note that the page I linked to (above) explicitly states that this test for near-equality has some weaknesses. Since this is a IEqualityComparer<T>
implementation, you can simply swap it out if it's not good enough for your purposes.
IEqualityComparer<T>
implementation must implement an equivalence relation, which implies that in all cases where two objects compare equal to a third, they must compare equal to each other. Any class which implements IEquatable<T>
or IEqualityComparer<T>
in a fashion contrary to the above is broken. –
Advisory IEqualityComparer<String>
which considers "hello" equal to both "Hello" and "hElLo" must consider "Hello" and "hElLo" equal to each other, but for most comparison methods that wouldn't be a problem. –
Advisory GetHashCode
should allow you to quickly determine whether two values differ. The rules are roughly as follows: (1) GetHashCode
must always produce the same hash code for the same value. (2) GetHashCode
should be fast (faster than Equals
). (3) GetHashCode
doesn't have to be precise (not as precise as Equals
). This means it may produce the same hash code for different values. The more precise you can make it, the better, but it's probably more important to keep it fast. –
Maharani You have already got the basic definition of what they are. In short, if you implement IEquatable<T>
on class T
, the Equals
method on an object of type T
tells you if the object itself (the one being tested for equality) is equal to another instance of the same type T
. Whereas, IEqualityComparer<T>
is for testing the equality of any two instances of T
, typically outside the scope of the instances of T
.
As to what they are for can be confusing at first. From the definition it should be clear that hence IEquatable<T>
(defined in the class T
itself) should be the de facto standard to represent uniqueness of its objects/instances. HashSet<T>
, Dictionary<T, U>
(considering GetHashCode
is overridden as well), Contains
on List<T>
etc make use of this. Implementing IEqualityComparer<T>
on T
doesn't help the above mentioned general cases. Subsequently, there is little value for implementing IEquatable<T>
on any other class other than T
. This:
class MyClass : IEquatable<T>
rarely makes sense.
On the other hand
class T : IEquatable<T>
{
//override ==, !=, GetHashCode and non generic Equals as well
public bool Equals(T other)
{
//....
}
}
is how it should be done.
IEqualityComparer<T>
can be useful when you require a custom validation of equality, but not as a general rule. For instance, in a class of Person
at some point you might require to test equality of two people based on their age. In that case you can do:
class Person
{
public int Age;
}
class AgeEqualityTester : IEqualityComparer<Person>
{
public bool Equals(Person x, Person y)
{
return x.Age == y.Age;
}
public int GetHashCode(Person obj)
{
return obj.Age.GetHashCode;
}
}
To test them, try
var people = new Person[] { new Person { age = 23 } };
Person p = new Person() { age = 23 };
print people.Contains(p); //false;
print people.Contains(p, new AgeEqualityTester()); //true
Similarly IEqualityComparer<T>
on T
doesn't make sense.
class Person : IEqualityComparer<Person>
True this works, but doesn't look good to eyes and defeats logic.
Usually what you need is IEquatable<T>
. Also ideally you can have only one IEquatable<T>
while multiple IEqualityComparer<T>
is possible based on different criteria.
The IEqualityComparer<T>
and IEquatable<T>
are exactly analogous to Comparer<T>
and IComparable<T>
which are used for comparison purposes rather than equating; a good thread here where I wrote the same answer :)
public int GetHashCode(Person obj)
should return obj.GetHashCode()
–
Congratulate obj.Age.GetHashCode
. will edit. –
Alleged person.GetHashCode
anywhere.... why would you override that? - We override because the whole point of IEqualityComparer
is to have a different comparison implementation according to our rules - the rules we really know well. –
Alleged Age
property, so I call Age.GetHashCode
. Age is of type int
, but whatever the type is the hash code contract in .NET is that different objects can have equal hash but equal objects cant ever have different hashes
. This is a contract we can blindly believe. Sure our custom comparers should adhere to this rule as well. If ever calling someObject.GetHashCode
breaks things, then it is the problem with implementors of type of someObject
, it's not our problem. –
Alleged IEqualityComparer is for use when the equality of two objects is externally implemented, e.g. if you wanted to define a comparer for two types that you did not have the source for, or for cases where equality between two things only makes sense in some limited context.
IEquatable is for the object itself (the one being compared for equality) to implement.
One compares two T
s. The other can compare itself to other T
s. Usually, you'll only need to use one at a time, not both.
© 2022 - 2024 — McMap. All rights reserved.
EqualityComparer<T>
instead of implementing the interface "becauseEqualityComparer<T>
tests equality usingIEquatable<T>
– TerrT
implementingIEquatable<T>
. Would a collection likeList<T>
have some kind of subtle bug in it otherwise? – Terr