为什么在Hashset或其他集合中使用继承对象时,Equals(object)会胜过Equals(T)?

时间:2015-02-10 15:05:44

标签: c# inheritance iequatable

我知道在实施Equals(object)时我始终必须覆盖GetHashCode()IEquatable<T>.Equals(T)

但是,我不明白,为什么在某些情况下Equals(object)胜过通用Equals(T)

例如,为什么会发生以下情况?如果我为接口声明IEquatable<T>并为其实现具体类型X,则在将这些类型的项目相互比较时,Equals(object)会调用常规Hashset<X>。在其中至少有一个边被强制转换为接口的所有其他情况下,调用正确的Equals(T)

这是一个代码示例来演示:

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)
}

2 个答案:

答案 0 :(得分:21)

当没有提供比较器时,

HashSet<T>调用EqualityComparer<T>.Default来获取默认的相等比较器。

EqualityComparer<T>.Default确定T是否实施IEquatable<T>。如果是,则使用它,如果不是,则使用object.Equalsobject.GetHashCode

您的Person对象实现IEquatable<IPerson>而非IEquatable<Person>

如果您有HashSet<Person>,则最终会检查Person是否为IEquatable<Person>,而不是object,因此它会使用HashSet<IPerson>方法。

如果您有IPerson,它会检查IEquatable<IPerson>hst.Contains((IPerson)t1); 是不是IEquatable,所以它会使用这些方法。


至于剩下的情况,为什么行:

Equals

调用HashSet<Person> Contains方法,即使它已在HashSet<Person>上调用。在这里,您在IPerson上呼叫HashSet<Person>.Contains并传入PersonIPerson要求参数为HashSet<Person>; IEnumerable<Person>不是有效参数。但是,IEnumerable<T>也是IEnumerable<IPerson>,由于Contains是协变的,这意味着它可以被视为IPerson,其IEnumerable.Contains扩展名为EqualityComparer<T>.Default方法(通过LINQ)接受Contains作为参数。

当没有提供时,

IEnumerable<IPerson>也使用EqualityComparer<IPerson>.Default来获得它的相等比较器。对于此方法调用,我们实际上正在IPerson上调用IEquatable<IPerson>,这意味着Equals正在检查{{1}}是否为{{1}} },它是,所以调用{{1}}方法。

答案 1 :(得分:2)

虽然IComparable<in T>T相反,但实现IComparable<Person>的任何类型都会自动被视为IComparable<IPerson>的实现,IEquatable<T>类型适用于密封类型,尤其是结构。 Object.GetHashCode()IEquatable<T>.Equals(T)Object.Equals(Object)一致的要求通常意味着后两种方法应该具有相同的行为,这反过来意味着其中一种方法应该链接到另一种方法。虽然将结构直接传递给正确类型的IEquatable<T>实现之间存在很大的性能差异,但与构造结构的boxed-heap-object类型的实例并具有Equals(Object)实现副本相比,结构数据,参考类型不存在这种性能差异。如果IEquatable<T>.Equals(T)Equals(Object)相同且T是可继承的引用类型,则之间没有任何有意义的区别:

bool Equals(MyType obj)
{
  MyType other = obj as MyType;
  if (other==null || other.GetType() != typeof(this))
    return false;
  ... test whether other matches this
}

bool Equals(MyType other)
{
  if (other==null || other.GetType() != typeof(this))
    return false;
  ... test whether other matches this
}

后者可以节省一个类型转换,但这不太可能产生足够的性能差异来证明有两种方法。