What is the difference between IEqualityComparer<T> and IEquatable<T>?
Asked Answered
W

5

189

I want to understand the scenarios where IEqualityComparer<T> and IEquatable<T> should be used. The MSDN documentation for both looks very similar.

Warrick answered 16/2, 2012 at 18:27 Comment(4)
MSDN sez:"This interface allows the implementation of customized equality comparison for collections." which of course is inferred in the selected answer. MSDN also recommends inheriting EqualityComparer<T> instead of implementing the interface "because EqualityComparer<T> tests equality using IEquatable<T>Terr
... the above suggests I should create a custom collection for any T implementing IEquatable<T>. Would a collection like List<T> have some kind of subtle bug in it otherwise?Terr
good explanation anotherchris.net/csharp/…Frierson
@RamilShavaleev the link is brokenKessel
S
149

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.

Sabina answered 16/2, 2012 at 18:29 Comment(3)
Same difference is between IComparable / IComparerPeccable
(Edited) IEqualityComparer<T> is an interface for an object (which is usually a lightweight class different from T) that provides comparison functions that operates on TVertex
Which one does Enumerable.Except use?Alleviate
M
78

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, then IEquatable<T> would be the right choice: This interface is supposed to be implemented only by T itself, so that one instance of T has internal knowledge of how to compare itself to another instance of T.

  • On the other hand, if there are several equally reasonable methods of comparing two Ts for equality, IEqualityComparer<T> would seem more appropriate: This interface is not meant to be implemented by T itself, but by other "external" classes. Therefore, when testing two instances of T for equality, because T has no internal understanding of equality, you will have to make an explicit choice of a IEqualityComparer<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.

Maharani answered 25/1, 2013 at 13:29 Comment(5)
The suggested comparison method for testing double-point equality is broken, since any legitimate 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
A better example would be comparisons between strings. It makes sense to have a string comparison method which regards strings as equal only if they contain the same sequence of bytes, but there are other useful comparison methods which also constitute equivalence relations, such as case-insensitive comparisons. Note that a 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
@supercat, thanks for the valuable feedback. It's likely that I won't get around to revising my answer in the next few days, so if you want, please feel free to edit as you see fit.Maharani
This is exactly what I needed and what I'm doing. However there is a need to override GetHashCode, in which it will return false once the values are different. How do you handle your GetHashCode?Dispensation
@teapeng: Not sure what specific use case you're talking about. Generally speaking, 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
A
39

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 :)

Alleged answered 6/12, 2012 at 18:40 Comment(6)
public int GetHashCode(Person obj) should return obj.GetHashCode()Congratulate
@HristoYankov should return obj.Age.GetHashCode. will edit.Alleged
Please explain why the comparer must make such an assumption about what the Hash of the object it compares? Your comparer does not know how Person computes its Hash, why would you override that?Congratulate
@HristoYankov Your comparer does not know how Person computes its Hash - Sure, that is why we have not directly called 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
In my example, I need to base my comparison off 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
This answer is AWESOME, dude. I could not found an understandable explanation ANYWHERE. Thanks!Spinode
I
14

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.

Inhalant answered 16/2, 2012 at 18:31 Comment(1)
You are the only one who actually raised the "Plugging in Equality" (external usage) issue. plus 1.Thalassography
C
5

One compares two Ts. The other can compare itself to other Ts. Usually, you'll only need to use one at a time, not both.

Companionway answered 16/2, 2012 at 18:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.