IEquatable<T>
究竟为您带来了什么?我唯一能想到的有用之处是在创建泛型类型并强制用户实现和编写一个良好的等于方法时。
我错过了什么吗?
IEquatable<T>
究竟为您带来了什么?我唯一能想到的有用之处是在创建泛型类型并强制用户实现和编写一个良好的等于方法时。
我错过了什么吗?
来自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
接口使意图更加清晰(比如考虑引用的库类源代码不可用的情况);2. 您可以将您的结构体传递给接受 IEquatable
接口或者在泛型约束上使用 IEquatable<T>
的方法中;3. 最重要的是,大多数集合操作的实现,例如 Contains
等,都不会调用您自定义的 Equals
方法(它们都检查类型是否实现了 IEquatbale
接口,否则就调用基类的 ValueType.Equals
方法)。 - nawfalEquals(T)
的实现,但这会增加更多的性能成本。在编程中通常按照约定来处理事物。在 .NET 中,如果要修改结构体的相等性,则应实现 IEquatable
(不要忘记实现 GetHashCode
,==
,!=
)以与之配合使用。 - nawfal除了其他答案之外,这里有一个非常好的原因来实现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 */;
}