我知道在实现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.Equals
和object.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
}
后者可以保存一个类型转换,但这不太可能产生足够的性能差异来证明使用两种方法的合理性。