5

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

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116
  • The documentation quoted gives you the answer - it uses a comparer which uses the `Equals(object)` and `GetHashCode` methods on `Person`. Why do you think it will use `PersonComparer`? – Lee Nov 26 '15 at 13:01
  • 1
    You just need to call `new PersonComparer()` instead of `PersonComparer.Default.`. – Dennis_E Nov 26 '15 at 13:03
  • "Sometimes you want two strings to be equal if they are exactly the same strings, sometimes you want…". Yeah, and when you have a bunch of "sometimes" then you might have a "default", which is what `Person` should do by itself and which `EqualityComparer.Default` would hence make use of. – Jon Hanna Nov 26 '15 at 17:30
  • "So I can't use the normal IEquatable, which should return false if the objects are not of the same type." Why would you define it that way? – Jon Hanna Nov 26 '15 at 17:36

3 Answers3

4

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

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Do you mean that the phrase "uses the overrides of ... by T" doesn't mean that the EqualityComparer.Equals(T x, T y) is called? Why do they exist then? – Harald Coppoolse Nov 26 '15 at 13:18
  • @HaraldDutch The overrides of `object.Equals` and `object.GetHashCode` would only exist if you override `IEquatable`. Not sure why the documentation expresses it this way, but they basically mean "it will defer to the default `object` implementation for these two methods". – Yuval Itzchakov Nov 26 '15 at 13:19
  • @HaraldDutch The implementation of `CreateComparer` inside `EqualityComparer` shows you that if it doesn't answer certain criteria's, [an `ObjectEqualityComparer` is returned](http://referencesource.microsoft.com/#mscorlib/system/collections/generic/equalitycomparer.cs,ac282b3e1817bb9b). Which will end up using `object.Equals` if the type has no custom `Equals` implementation. – Yuval Itzchakov Nov 26 '15 at 13:25
2

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);
Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
2

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.

Glenn Slayden
  • 17,543
  • 3
  • 114
  • 108
György Kőszeg
  • 17,093
  • 6
  • 37
  • 65