20

我知道我总是必须覆盖Equals(object)并且GetHashCode()在实施IEquatable<T>.Equals(T).

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

例如为什么会发生以下情况?如果我声明IEquatable<T>一个接口并为它实现一个具体类型,则在将这些类型的项目相互比较时X,一般Equals(object)会被 a 调用。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)
}
4

2 回答 2

21

HashSet<T>当没有提供比较器时调用EqualityComparer<T>.Default以获取默认的相等比较器。

EqualityComparer<T>.Default确定是否T实现IEquatable<T>. 如果是,它使用它,如果不是,它使用object.Equalsand object.GetHashCode

您的Person对象实现IEquatable<IPerson>not IEquatable<Person>

当您拥有 aHashSet<Person>时,它最终会检查是否Person是 a IEquatable<Person>,而它不是,因此它使用这些object方法。

当你有 aHashSet<IPerson>时,它会检查是否IPerson是 a IEquatable<IPerson>,它是,所以它使用这些方法。


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

hst.Contains((IPerson)t1);

调用该IEquatable Equals方法,即使它在HashSet<Person>. 在这里,您调用ContainsaHashSet<Person>并传入 a IPersonHashSet<Person>.Contains要求参数为 a Person; anIPerson不是有效参数。然而, aHashSet<Person>也是一个IEnumerable<Person>,并且因为IEnumerable<T>是协变的,这意味着它可以被视为一个IEnumerable<IPerson>,它有一个Contains扩展方法(通过 LINQ),它接受一个IPerson作为参数。

IEnumerable.Contains也用于EqualityComparer<T>.Default在没有提供相等比较器时获取它的相等比较器。在这个方法调用的情况下,我们实际上是在调用Containsan IEnumerable<IPerson>,这意味着EqualityComparer<IPerson>.Default检查是否IPerson是 an IEquatable<IPerson>,它是,所以该Equals方法被调用。

于 2015-02-10T15:12:39.630 回答
2

尽管IComparable<in T>相对于 是逆变的T,因此任何实现的类型都IComparable<Person>将自动被视为 的实现IComparable<IPerson>,但该类型IEquatable<T>旨在与密封类型一起使用,尤其是结构。Object.GetHashCode()与两者一致IEquatable<T>.Equals(T)的要求Object.Equals(Object)通常意味着后两种方法的行为应该相同,这反过来又意味着它们中的一个应该链接到另一个。虽然将结构直接传递给适当类型的实现之间存在很大的性能差异IEquatable<T>,但与构造结构的盒装堆对象类型的实例并具有Equals(Object)实现从中复制结构数据,引用类型不存在这种性能差异。如果IEquatable<T>.Equals(T)andEquals(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
}

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

于 2015-02-10T19:26:54.707 回答