What is the difference between IEqualityComparer

2019-01-13 00:10发布

问题:

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

回答1:

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.



回答2:

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.



回答3:

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



回答4:

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.



回答5:

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