【问题标题】:EqualityComparerer<T>.Default.Equals() vs object.Equals() and polymorphismEqualityComparerer<T>.Default.Equals() vs object.Equals() 和多态性
【发布时间】:2013-12-11 18:11:20
【问题描述】:

再次讨论平等时,我偶然发现了EqualityComparer&lt;T&gt;.Default.Equals()。我更喜欢为引用类型调用此方法,而不是 object.Equals()
现在我觉得我大错特错了。

object.Equals() 使用可覆盖的实例 Equals() 方法提供正确的多态行为,而 EqualityComparer&lt;T&gt;.Default.Equals() 调用 IEquatable&lt;T&gt;.Equals() 如果已实现。

现在考虑这个小程序:

public class Class1 : IEquatable<Class1>
{
    public int Prop1 { get; set; }

    public bool Equals(Class1 other)
    {
        if (other == null)
            return false;

        return Prop1 == other.Prop1;
    }

    public override bool Equals(object obj)
    {
        if (obj == null || GetType() != obj.GetType())
        {
            return false;
        }

        return Equals(obj as Class1);
    }
}

public class Class2 : Class1, IEquatable<Class2>
{
    public int Prop1 { get; set; }
    public int Prop2 { get; set; }

    public bool Equals(Class2 other)
    {
        if (other == null)
            return false;

        return Prop1 == other.Prop1 && Prop2 == other.Prop2;
    }

    public override bool Equals(object obj)
    {
        if (obj == null || GetType() != obj.GetType())
        {
            return false;
        }

        return Equals(obj as Class2);
    }
}


class Program
{
    static void Main(string[] args)
    {
        var c1 = new Class1 {Prop1 = 10};
        var c2 = new Class2 {Prop1 = 10, Prop2 = 5};
        var c3 = new Class2 {Prop1 = 10, Prop2 = 15};

        Console.WriteLine("Object.Equals()");
        Console.WriteLine("C1=C2 {0}",Equals(c1,c2));
        Console.WriteLine("C2=C1 {0}",Equals(c2, c1));
        Console.WriteLine("C2=C3 {0}",Equals(c2, c3));
        Console.WriteLine("C3=C2 {0}", Equals(c3, c2));

        var dec1 = EqualityComparer<Class1>.Default;

        Console.WriteLine();
        Console.WriteLine("EqualityComparer<Class1>.Default.Equals");
        Console.WriteLine("C1=C2 {0}", dec1.Equals(c1, c2));
        Console.WriteLine("C2=C1 {0}", dec1.Equals(c2, c1));
        Console.WriteLine("C2=C3 {0} BUG?", dec1.Equals(c2, c3));
        Console.WriteLine("C3=C2 {0} BUG?", dec1.Equals(c3, c2));

        Console.ReadKey();
    }
}

它显示了在相等语义中带来不一致是多么容易:

Object.Equals()
C1=C2 错误
C2=C1 错误
C2=C3 错误
C3=C2 错误

EqualityComparer.Default.Equals
C1=C2 错误
C2=C1 错误
C2=C3 真正的BUG?
C3=C2 真正的BUG?

不过MSDN Documentation 建议:

给实现者的注意事项 如果你实现了 Equals,你还应该 覆盖 Object.Equals(Object) 的基类实现和 GetHashCode 使它们的行为与 IEquatable.Equals 方法。如果您确实覆盖了 Object.Equals(Object), 在对静态的调用中也会调用您覆盖的实现 类上的 Equals(System.Object, System.Object) 方法。在 此外,您应该重载 op_Equality 和 op_Inequality 运营商。这确保了所有相等性测试返回一致 结果,示例说明了。

从这一刻开始,我认为没有理由为引用类型实现IEquatable&lt;T&gt;。 谁能告诉我什么时候有任何意义? 当我们以不同的方式(作为基本类型)看待类型时,我真的应该将不同的相等行为视为不一致吗?

【问题讨论】:

  • 就我个人而言,我所做的是使用object.Equals() 覆盖调用IEquatable&lt;T&gt;.Equals()。除了蓬松的“设计简洁”之外,没有任何技术原因——简单的对象不能与任何其他对象“等同”,这就是为什么实现一个指定你可以与谁进行比较的接口对我来说是有意义的。
  • @我会在最后一次索赔中打电话给 BS。你看过那个方法的实现了吗?
  • 你没有让object.Equals(object) 等同于IEquatable&lt;Class1&gt;.Equals(Class1) 中的Class1 就像你应该拥有的那样(如果你将它与Class2 进行比较,它们并不总是具有相同的结果),所以你会看到令人困惑的结果。更常见的等同方式是说对象必须具有完全相同的类型才能被视为相等(如您的 Equals(object) 方法)
  • 这只是Class1IEquatable&lt;T&gt;.Equals 的不良实现,将实现虚拟化并在Class2 中覆盖它
  • @Will "If the two objects do not represent the same object reference and neither is null, it calls objA.Equals(objB) and returns the result." 除非某个对象有可能以某种方式与自身引用相等但与自身不相等,否则最终将返回与调用 x.Equals(y) 相同的结果会的。

标签: c# .net equality iequalitycomparer iequatable


【解决方案1】:

今天我问自己将IEquatable&lt;T&gt; 添加到课程时会产生什么后果,我找到了您的问题。
然后我测试了你的代码。对于阅读本文的其他人,这里有一个答案,而不仅仅是“只是这样做才能使其工作”。

首先,这不是错误。
您的问题是,您指定了一个 EqualityComparer&lt;Class1&gt;,它仅在 class1 中由 public bool Equals(Class1 other) 实现。
因此,dec1.Equals(c2, c3) 将调用此函数,其中仅比较 class1 的内容。

从您的评论BUG? 我可以看出您希望class2 的内容也可以进行比较,就像其他人所期望的那样。为此,您需要更改
public bool Equals(Class1 other)
进入
public virtual bool Equals(Class1 other)
并在class2 中覆盖此函数,然后您还可以比较class2 的内容。
但这可能会导致一个非常奇怪的结构。因此,为了完整起见,这是我的实现方式:

基类中,只进行类型检查:

//--------------------------------------------------------------------------
public static bool operator == (CClass1 i_value1, CClass1 i_value2)
{
  if (ReferenceEquals (i_value1, i_value2))
    return true;
  if (ReferenceEquals (null, i_value1))
    return false;

  return (i_value1.Equals (i_value2));
}

//--------------------------------------------------------------------------
public static bool operator != (CClass1 i_value1, CClass1 i_value2)
{
  return !(i_value1 == i_value2);
}

///-------------------------------------------------------------------------
public sealed override bool Equals (object i_value)
{
  if (ReferenceEquals (null, i_value))
    return false;
  if (ReferenceEquals (this, i_value))
    return true;

  if (i_value.GetType () != GetType ())
    return false;

  return Equals_EXEC ((CClass1)i_value);
}

///-------------------------------------------------------------------------
public bool Equals (CClass1 i_value)  // not virtual, don't allow overriding!
{
  if (ReferenceEquals (null, i_value))
    return false;
  if (ReferenceEquals (this, i_value))
    return true;

  if (i_value.GetType () != GetType ())
    return false;

  return Equals_EXEC (i_value);
}

仍在基类中,内容检查:

///-------------------------------------------------------------------------
protected override bool Equals_EXEC (CClass1 i_value)
{
  return Equals_exec (i_value);
}

//--------------------------------------------------------------------------
private bool Equals_exec (CClass1 i_value)
{
  return variable1 == i_value.variable1
      && variable2 == i_value.variable2
      && ... ;
}

派生类中,内容检查:

///-------------------------------------------------------------------------
protected override bool Equals_EXEC (CClassN i_value)
{
  return base.Equals_EXEC (i_value)
      && Equals_exec (i_value as CClassN);
}

//--------------------------------------------------------------------------
private bool Equals_exec (CClassN i_value)
{
  return variable5 == i_value.variable5
      && variable6 == i_value.variable6
      && ... ;
}

【讨论】:

【解决方案2】:

无论对错,这就是我倾向于在基类和派生类上实现Equals(Object)IEquatable&lt;T&gt;.Equals(T) 的方式。

public class Class1 : IEquatable<Class1>
{    
    public sealed override bool Equals(object obj)
    {
        return Equals(obj as Class1);
    }

    public virtual bool Equals(Class1 obj)
    {
        if(ReferenceEquals(obj, null))
            return false;

        // Some property checking
    }
}

public class Class2 : Class1, IEquatable<Class2>
{
    public sealed override bool Equals(Class1 obj)
    {
        return Equals(obj as Class2);
    }

    public virtual bool Equals(Class2 obj)
    {
        if(!base.Equals(obj))
            return false;

        // Some more property checking
    }
}

public class Class3 : Class2, IEquatable<Class3>
{
    public sealed override bool Equals(Class2 obj)
    {
        return Equals(obj as Class3);
    }

    public virtual bool Equals(Class3 obj)
    {
        if(!base.Equals(obj))
            return false;

        // Some more property checking
    }
}

对于引用类型,实现IEquatable&lt;T&gt; 的好处是微不足道的,如果你有两个T 类型的实例,你可以直接调用T.Equals(T)。而不是T.Equals(Object),后者随后需要对参数执行类型检查。

IEquatable&lt;T&gt; 的主要用途是用于值类型,其中装箱实例存在开销。

【讨论】:

  • 好吧,至少你的暗示更正确。但是您需要重写两种方法,而不仅仅是一个 object.Equals()。那么这里IEquatable的利润是多少呢?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-31
  • 1970-01-01
  • 1970-01-01
  • 2010-09-14
  • 1970-01-01
  • 2012-01-28
相关资源
最近更新 更多