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



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

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

例如,为什么会发生以下情况?如果我为接口声明IEquatable<T>并为其实现具体类型X,则在比较这些类型的项目时,Hashset<X>会调用一般Equals(object)。在至少将一端转换为接口的所有其他情况下,将调用正确的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)
}

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> 上调用。 在这里,您在HashSet<Person>上呼叫Contains并通过IPerson. HashSet<Person>.Contains要求参数为Person;IPerson不是有效的参数。 然而,HashSet<Person>也是一个IEnumerable<Person>,并且由于IEnumerable<T>是协变的,这意味着它可以被视为一个IEnumerable<IPerson>,它有一个Contains扩展方法(通过LINQ(,它接受一个IPerson作为参数。

IEnumerable.Contains 还使用 EqualityComparer<T>.Default 来获取其相等比较器(如果未提供(。 在此方法调用的情况下,我们实际上是在IEnumerable<IPerson>上调用Contains,这意味着EqualityComparer<IPerson>.Default正在检查IPerson是否是一个IEquatable<IPerson>,它是,因此调用Equals方法。

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

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

相关内容

最新更新