EqualityComparerer <t> .Default.Equals()vs object.Equals()和polymorphism </t>

时间:2013-11-26 14:29:05

标签: c# .net equality iequalitycomparer iequatable

再一次讨论平等,我偶然发现EqualityComparer<T>.Default.Equals()。我更喜欢将此方法称为参考类型而不是object.Equals() 现在我觉得我错了。

object.Equals()使用可覆盖的实例Equals()方法提供正确的多态行为,而EqualityComparer<T>.Default.Equals()则调用IEquatable<T>.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&lt; Class1&gt; .Default.Equals
  C1 = C2假   C2 = C1假   C2 = C3真的BUG?
  C3 = C2真的BUG?

但是MSDN Documentation推荐:

  

对实施者的说明如果实施Equals,您也应该   覆盖Object.Equals(Object)和的基类实现   GetHashCode使他们的行为与。的行为一致   IEquatable&lt; T&gt; .Equals方法。如果你重写Object.Equals(Object),   在调用static时也会调用重写的实现   在类上等于(System.Object,System.Object)方法。在   另外,你应该重载op_Equality和op_Inequality   运营商。这可确保所有相等的测试返回一致   结果,这个例子说明了这一点。

从这一刻开始,我认为没有理由为引用类型实现IEquatable<T>。 谁有任何意义可以告诉我什么? 当我们以不同的方式查看类型(作为基类型)时,我是否应该将不同的相等行为视为不一致?

2 个答案:

答案 0 :(得分:3)

今天我问自己,将IEquatable<T>添加到课程中会导致什么后果,我发现了您的问题。
然后,我测试了您的代码。对于阅读此书的其他所有人,这是一个答案,而不仅仅是“像那样做就可以使它起作用”。

首先,这不是错误。
您的问题是,您指定了EqualityComparer<Class1>,而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
      && ... ;
}

派生类中,内容检查:

axios

答案 1 :(得分:2)

无论是对还是错,我都倾向于在基类和派生类上实现Equals(Object)IEquatable<T>.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<T>的好处是微不足道的,如果您有两个类型T的实例,则可以直接调用T.Equals(T)。而不是T.Equals(Object),后来需要对参数执行类型检查。

IEquatable<T>的主要目的是用于值类型,其中装箱实例会产生开销。