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:
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(); }
}
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.
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();
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With