Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why implement IEqualityComparer<T> in a separate class

Tags:

c#

compare

linq

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(); }
}
like image 487
Alexander Derck Avatar asked Feb 10 '16 11:02

Alexander Derck


3 Answers

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.

like image 200
Ivan Stoev Avatar answered Oct 21 '22 05:10

Ivan Stoev


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();
like image 26
Dmitry Bychenko Avatar answered Oct 21 '22 05:10

Dmitry Bychenko


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.

like image 3
Evren Kuzucuoglu Avatar answered Oct 21 '22 05:10

Evren Kuzucuoglu