我知道在实施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)
}
答案 0 :(得分:21)
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>
Contains
方法,即使它已在HashSet<Person>
上调用。在这里,您在IPerson
上呼叫HashSet<Person>.Contains
并传入Person
。 IPerson
要求参数为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
}
后者可以节省一个类型转换,但这不太可能产生足够的性能差异来证明有两种方法。