Reputation: 14498
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(); }
}
Upvotes: 7
Views: 4646
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
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
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
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