Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EqualityComparer<T>.Default doesn't return the derived EqualityComparer

I have a class Person, and created an equality comperer class derived from EqualityComparer < Person >. Yet the default EqualityComparer doesn't call the Equals function of my equality comparer

According to MSDN EqualityComparer < T > .Default property:

The Default property checks whether type T implements the System.IEquatable interface and, if so, returns an EqualityComparer that uses that implementation. Otherwise, it returns an EqualityComparer that uses the overrides of Object.Equals and Object.GetHashCode provided by T.

In the (simplified) example below, class Person does not implement implement System.IEquatable < Person >. So I'd expect that PersonComparer.Default would return an instance of PersonComparer.

Yet PersonComparer.Equals is not called. There is no debug output and the returned value is false.

public class Person
{
    public string Name { get; set; }
}

public class PersonComparer : EqualityComparer<Person>
{
    public override bool Equals(Person x, Person y)
    {
        Debug.WriteLine("PersonComparer.Equals called");
        return true;
    }

    public override int GetHashCode(Person obj)
    {
        Debug.WriteLine("PersonComparer.GetHasCode called");
        return obj.Name.GetHashCode();
    }
}

public static void Main()
{
    Person x = new Person() { Name = "x" };
    Person y = new Person() { Name = "x" };
    bool b1 = PersonComparer.Default.Equals(x, y);
}

Question: What am I doing wrong?

In case you might wonder why I don't want to implement IEquatable < Person >.

My problem is comparable with the comparison of strings. Sometimes you want two strings to be equal if they are exactly the same strings, sometimes you want to ignore case, and sometimes you want to treat characters as óò etc all as if they are the character o.

In my case: I store a Person in something, which might be a database, but it might as well be a File or a MemoryStream. Upon return I get an identifier, which in case of a database is of course the primary key. With this key I am able to retrieve an object with the same values.

I want to test this in a unit Test: I you put something in it, you should get a key that can be used to retrieve the item. Alas, the database doesn't return the same Person, but a derived class of Person (At least when using EF 6). So I can't use the normal IEquatable, which should return false if the objects are not of the same type. That's why I wanted to use a special comparer, one that declares two Persons equal if they have the same values for the properties, even if they are both different derived classes from Person. Very comparable as a string comparer that accepts O and o and ó to be equal

like image 817
Harald Coppoolse Avatar asked Nov 26 '15 12:11

Harald Coppoolse


3 Answers

Let's re-read the quote you added:

The Default property checks whether type T implements the System.IEquatable interface and, if so, returns an EqualityComparer that uses that implementation.

So, the Default property looks for an implementation of IEqutable<T>, which your Person does not provide.

If the object doesn't implement IEquatable<T>, then:

Otherwise, it returns an EqualityComparer that uses the overrides of Object.Equals and Object.GetHashCode provided by T.

Which shows you exactly why object.Equals and object.GetHashCode are the ones being called. You have two choices, either use new PersonComparer().Equals(), or implement IEquatable<Person> on your type (if such a single implementation is possible).

like image 93
Yuval Itzchakov Avatar answered Dec 11 '22 16:12

Yuval Itzchakov


That is because the Default property of EqualityComparer<T> does not return a PersonComparer, but a ObjectEqualityComparer<T>. And that ObjectEqualityComparer<T>, as you referenced the documentation, compares using the Equals on Person.

See the actual source. At line 89 it returns the ObjectEqualityComparer<T>.

There is actually nothing wrong with your code, as you can see when you actually try to run your code on an instance of PersonComparer:

bool b1 = new PersonComparer().Equals(x, y);
like image 28
Patrick Hofman Avatar answered Dec 11 '22 17:12

Patrick Hofman


You have mixed up some things, but it's not surprising since, everyone must admit that the .NET Framework has a bit too many possibilities for equality comparison.

You should implement the IEqualityComparer<T> only if you want to specify special comparison logic for specific situations, such as to a Dictionary<TKey, TValue>, for example.

The EqualityComparer<T> is confusingly an overridable type in .NET; however, it is not intended that you override it, and there's no use in doing so. It provides a default comparer for generic types and it will call your IEquatable<T> implementation if you use T in a List<T> (Contains, IndexOf, etc.) or when T is a key of a dictionary and you didn't pass any custom IEqualityComparer to the dictionary.

like image 42
György Kőszeg Avatar answered Dec 11 '22 15:12

György Kőszeg