何时使用IEquatable和为什么

时间:2010-03-19 11:27:10

标签: c#

我只是不明白这一点--Ilququatable给你带来了什么?

我可以看到它有用的唯一原因是创建泛型类型并强制用户实现并编写一个好的equals方法。

我缺少什么

5 个答案:

答案 0 :(得分:36)

来自MSDN

  

IEquatable(T)接口由Dictionary(TKey, TValue)List(T)LinkedList(T)等通用集合对象在{{1}等方法中进行相等性测试时使用},ContainsIndexOfLastIndexOf

Remove实现将需要少一个这些类的强制转换,因此将比标准IEquatable<T>方法稍快一些。作为示例,请参阅两种方法的不同实现:

object.Equals

答案 1 :(得分:30)

我很惊讶这里没有提到最重要的原因。

IEquatable<>主要是针对结构引入的,原因有两个:

  1. 对于值类型(读取结构),非泛型Equals(object)需要装箱。 IEquatable<>允许结构实现强类型Equals方法,以便不需要装箱。

  2. 对于结构体,Object.Equals(Object)的默认实现(System.ValueType中的重写版本)通过使用反射来比较类型中每个字段的值来执行值相等性检查。当实现者覆盖结构中的虚拟Equals方法时,目的是提供一种更有效的方法来执行值相等性检查,并可选择将结果基于结构的一个或多个字段的属性进行比较。

  3. 这两项都提高了性能。

    引用类型(读取类)不会受益。 IEquatable<>实现确实可以避免System.Object的强制转换,但这是一个非常微不足道的收获。我仍然希望为我的类实现IEquatable<>,因为它在逻辑上使得intent明确。

答案 2 :(得分:11)

我使用IEquatable<T>相当多,尽管从纯粹的技术角度来看,它并没有给我任何特别的好处。覆盖System.Object.Equals可以为您提供相同的功能。

但是,我喜欢实施IEquatable<T>显性。我非常使用Domain-Driven Design实体值对象的概念,特别是对于值对象使用IEquatable<T>,因为它表示一个类型有明确的平等。

答案 3 :(得分:2)

对于其他答案,这里有一个非常好的理由来实现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;
}

在某些情况下(例如使用值类型作为字典中的键),它可以在一次犯规中谋杀性能。

答案 4 :(得分:0)

根据文档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 */;
}