I am aware of the fact that I always have to override Equals(object)
and GetHashCode()
when implementing IEquatable<T>.Equals(T)
.
However, I don't understand, why in some situations the Equals(object)
wins over the generic Equals(T)
.
For example why is the following happening? If I declare IEquatable<T>
for an interface and implement a concrete type X
for it, the general Equals(object)
is called by a Hashset<X>
when comparing items of those type against each other. In all other situations where at least one of the sides is cast to the Interface, the correct Equals(T)
is called.
Here's a code sample to demonstrate:
public interface IPerson : IEquatable<IPerson> { }
//Simple example implementation of Equals (returns always true)
class Person : IPerson
{
public bool Equals(IPerson other)
{
return true;
}
public override bool Equals(object obj)
{
return true;
}
public override int GetHashCode()
{
return 0;
}
}
private static void doEqualityCompares()
{
var t1 = new Person();
var hst = new HashSet<Person>();
var hsi = new HashSet<IPerson>();
hst.Add(t1);
hsi.Add(t1);
//Direct comparison
t1.Equals(t1); //IEquatable<T>.Equals(T)
hst.Contains(t1); //Equals(object) --> why? both sides inherit of IPerson...
hst.Contains((IPerson)t1); //IEquatable<T>.Equals(T)
hsi.Contains(t1); //IEquatable<T>.Equals(T)
hsi.Contains((IPerson)t1); //IEquatable<T>.Equals(T)
}
HashSet<T>
calls EqualityComparer<T>.Default
to get the default equality comparer when no comparer is provided.
EqualityComparer<T>.Default
determines if T
implementsIEquatable<T>
. If it does, it uses that, if not, it uses object.Equals
and object.GetHashCode
.
Your Person
object implements IEquatable<IPerson>
not IEquatable<Person>
.
When you have a HashSet<Person>
it ends up checking if Person
is an IEquatable<Person>
, which its not, so it uses the object
methods.
When you have a HashSet<IPerson>
it checks if IPerson
is an IEquatable<IPerson>
, which it is, so it uses those methods.
As for the remaining case, why does the line:
hst.Contains((IPerson)t1);
call the IEquatable
Equals
method even though its called on the HashSet<Person>
. Here you're calling Contains
on a HashSet<Person>
and passing in an IPerson
. HashSet<Person>.Contains
requires the parameter to be a Person
; an IPerson
is not a valid argument. However, a HashSet<Person>
is also an IEnumerable<Person>
, and since IEnumerable<T>
is covariant, that means it can be treated as an IEnumerable<IPerson>
, which has a Contains
extension method (through LINQ) which accepts an IPerson
as a parameter.
IEnumerable.Contains
also uses EqualityComparer<T>.Default
to get its equality comparer when none is provided. In the case of this method call we're actually calling Contains
on an IEnumerable<IPerson>
, which means EqualityComparer<IPerson>.Default
is checking to see if IPerson
is an IEquatable<IPerson>
, which it is, so that Equals
method is called.