Alexander Derck
Alexander Derck

Reputation: 14498

Why implement IEqualityComparer<T> in a separate class

When I was looking up the generic IEqualityComparer interface on msdn I noticed the interface was implemented in a separate 'comparer' class, opposed to IEquatable<T> which is implemented in the class itself. When I searched for some more examples, every single one was using a separate class and that got me wondering: why not implement it on the class itself?

I can imagine overriding object.Equals and object.GetHashCode isn't considered good practice because it's used in a lot of different situations, but even msdn says (emphasis mine):

This interface allows the implementation of customized equality comparison for collections.

so its uses are pretty much limited to Linq. There's only 2 reasons I can think of why to define a separate comparer class:

  1. Different methods on a collection of the class require a different comparer.
  2. The class is big and instantiating another object of it isn't desired (although if that's really the issue, why isn't having a whole collection of it not bad?).

So my question is:

Is there any particular reason that I overlooked which causes everyone to define another comparerclass just for comparing instead of just implementing the interface on the class itself (which would not be worse in my opinion to say the least)?

A small example:

public static void Main(string[] args)
{
    Test t1 = new Test { id = 1, date = default(DateTime) };
    Test t2 = new Test { id = 1, date = default(DateTime) };
    Test t3 = new Test { id = 0, date = default(DateTime) };
    List<Test> testList = new List<Test>{ t1, t2, t3 };

    //Same result
    int distinctCountClass = testList.Distinct(new Test()).Count();
    int distinctCountComparerClass = testList.Distinct(new TestComparer()).Count();
}

public partial class Test
{
    public int id { get; set; }
    public DateTime date { get; set; }
}

public partial class Test : IEqualityComparer<Test>
{
    public bool Equals(Test x, Test y) { return x.id == y.id && x.date == y.date; }
    public int GetHashCode(Test obj) { return obj.id.GetHashCode(); }
}

public class TestComparer : IEqualityComparer<Test>
{
    public bool Equals(Test x, Test y) { return x.id == y.id && x.date == y.date; }
    public int GetHashCode(Test obj) { return obj.id.GetHashCode(); }
}

Upvotes: 7

Views: 4646

Answers (4)

ccoutinho
ccoutinho

Reputation: 4556

It is a good practice to implement IEqualityComparer<T> rather than IEquatable<T>, because when a class implements the IEquatable<T> interface, it enters a contract in which it states "I know how to compare two instances of type T or any type derived from T for equality". However if that class is derived, it is very unlikely that the base class will know how to make a meaningful comparison. Therefore, that implicit contract is now broken.

Alternatively, the class whose instances we want to be comparable, could be made sealed, but for the reasons already stated in this conversation thread, the other approach is much more elegant.

Upvotes: 0

Ivan Stoev
Ivan Stoev

Reputation: 205599

why not implement it on the class itself?

Because it makes no sense. The whole purpose of the IEqualityComparer<T> is to be implemented outside the type T because it targets the "reason 1" from your post.

If you want the class itself to implement the equality logic, then you are expected to implement IEquatable<T> which is provided specifically for such scenario, and EqualityComparer<T>.Default will provide the necessary bridge to your implementation anytime IEqualityComparer<T> is needed and not specified explicitly.

Since the class can provide only one hardcoded logic without any dynamic behavior and/or options, it's considered to be the default equality logic, hence the name of the static EqualityProvider<T> property providing access to it.

Upvotes: 5

Dmitrii Bychenko
Dmitrii Bychenko

Reputation: 186678

IComparer<T> as well as IEqualityComparer<T> work with two instances of T so they have no need to be implemented as a part of T class; however, implementing IEqualityComparer<T> within the T is a good practice, the scheme can be

  public partial class Test {
    private class TestComparer : IEqualityComparer<Test> {
      public bool Equals(Test x, Test y) { 
        return x.id == y.id && x.date == y.date; 
      }

      public int GetHashCode(Test obj) { 
        return obj.id.GetHashCode(); 
      }
    }

    // Please, note "static"
    public static IEqualityComparer<Test> MyTestComparer {get;} = new TestComparer();

    public int id { get; set; }
    public DateTime date { get; set; }
    ...
  }

In this case you just use the comparer you want:

int distinctCountComparerClass = testList.Distinct(Test.MyTestComparer).Count();

Upvotes: 4

Evren Kuzucuoglu
Evren Kuzucuoglu

Reputation: 3885

Simply put, this way you can use different ways of comparing objects from the same class depending on the context.

It's basically inversion of control: it is not for the class itself to decide how another class might want to compare its instances.

Upvotes: 3

Related Questions