Marwie Marwie - 2 months ago 10
C# Question

Why does Equals(object) win over Equals(T) when using an inherited object in Hashset or other Collections?

I am aware of the fact that I always have to override

when implementing

However, I don't understand, why in some situations the
wins over the generic

For example why is the following happening? If I declare
for an interface and implement a concrete type
for it, the general
is called by a
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
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>();


//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:


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.