到底IEquatable<T>
给你买了什么?我认为它有用的唯一原因是在创建泛型类型并强制用户实现和编写良好的 equals 方法时。
我错过了什么?
从MSDN:
该
IEquatable(T)
接口由通用集合对象(例如Dictionary(TKey, TValue)
,和 )使用List(T)
,并且在测试诸如、、和LinkedList(T)
等方法中的相等性时使用。Contains
IndexOf
LastIndexOf
Remove
这些类的实现将需要少一次强制转换,因此将比其他情况下使用的标准方法IEquatable<T>
稍快。object.Equals
作为一个例子,看看这两种方法的不同实现:
public bool Equals(T other)
{
if (other == null)
return false;
return (this.Id == other.Id);
}
public override bool Equals(Object obj)
{
if (obj == null)
return false;
T tObj = obj as T; // The extra cast
if (tObj == null)
return false;
else
return this.Id == tObj.Id;
}
我很惊讶这里没有提到最重要的原因。
IEquatable<>
引入结构主要有两个原因:
对于值类型(读取结构),非泛型Equals(object)
需要装箱。IEquatable<>
让结构实现强类型Equals
方法,这样就不需要装箱。
对于结构体,Object.Equals(Object)
(在 中被覆盖的版本System.ValueType
)的默认实现通过使用反射来比较类型中每个字段的值来执行值相等检查。当实现者覆盖结构中的虚拟 Equals 方法时,目的是提供一种更有效的方法来执行值相等检查,并可选择将比较基于结构的字段或属性的某个子集。
两者都提高了性能。
引用类型(读取类)没有那么多好处。该IEquatable<>
实现确实可以让您避免强制转换,System.Object
但这是一个非常微不足道的收获。我仍然喜欢IEquatable<>
为我的类实现,因为它在逻辑上使意图明确。
除了其他答案之外,这里有一个很好的理由来实现IEquatable<T>
(并且显然也覆盖Equals(object)
)值类型。只需查看以ValueType.Equals(object)
其他方式调用的默认代码。它是一个绝对的性能杀手,它引入了装箱、类型评估,如果任何字段是引用类型,最终会依靠反射。
public override bool Equals(object obj)
{
if (obj == null)
{
return false;
}
RuntimeType type = (RuntimeType) base.GetType();
RuntimeType type2 = (RuntimeType) obj.GetType();
if (type2 != type)
{
return false;
}
object a = this;
if (CanCompareBits(this))
{
return FastEqualsCheck(a, obj);
}
FieldInfo[] fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
for (int i = 0; i < fields.Length; i++)
{
object obj3 = ((RtFieldInfo) fields[i]).UnsafeGetValue(a);
object obj4 = ((RtFieldInfo) fields[i]).UnsafeGetValue(obj);
if (obj3 == null)
{
if (obj4 != null)
{
return false;
}
}
else if (!obj3.Equals(obj4))
{
return false;
}
}
return true;
}
在某些情况下(例如使用值类型作为字典中的键),它可以一举扼杀性能。
根据文档IEquality<T>
用于提高性能(它可以防止装箱。)在通用集合中特别有用。
如果要IEquatable<T>
在类层次结构中实现,可以使用以下模式。它防止派生(包括兄弟)类是平等的。如果派生类不需要相等,您可以跳过IEquatable<Derived>
,但您需要覆盖它CanEqual
以防止它与基类相等(除非它们当然应该被视为相等)。
虽然我认为不拳击的收益会小于拥有的成本CanEqual
。在这种情况下,您应该密封您的类型并且不再需要CanEqual
. 密封还具有一些性能优势。
public class Base : IEquatable<Base>
{
protected virtual bool CanEqual(Base other) => other is Base;
public override bool Equals(object obj) => obj is Base other && Equals(other);
public bool Equals(Base other) => this.CanEqual(other) && other.CanEqual(this) /* && base logic */;
}
public class Derived : Base, IEquatable<Derived>
{
protected override bool CanEqual(Base other) => other is Derived;
public override bool Equals(object obj) => obj is Derived other && Equals(other);
public bool Equals(Derived other) => this.CanEqual(other) && other.CanEqual(this) /* && derived logic */;
}