简体   繁体   English

平等比较者<T> .Default.Equals() 与 object.Equals() 和多态

[英]EqualityComparerer<T>.Default.Equals() vs object.Equals() and polymorphism

Once again discussing equality I stumbled on EqualityComparer<T>.Default.Equals() .再次讨论平等我偶然发现了EqualityComparer<T>.Default.Equals() I prefer to call this method for reference types rather than object.Equals() .我更喜欢为引用类型而不是object.Equals()调用此方法。
Now I think I was dreadfully wrong.现在我觉得我大错特错了。

object.Equals() uses overridable instance Equals() method providing correct polymorphic behavior whereas EqualityComparer<T>.Default.Equals() calls IEquatable<T>.Equals() if it's implemetned. object.Equals()使用可覆盖的实例Equals()方法提供正确的多态行为,而EqualityComparer<T>.Default.Equals()调用IEquatable<T>.Equals()如果它被实现。

Now consider this small program:现在考虑这个小程序:

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();
    }
}

It shows how easy it is to bring inconsistency in equality semantics:它显示了在相等语义中带来不一致是多么容易:

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

EqualityComparer<Class1>.Default.Equals EqualityComparer<Class1>.Default.Equals
C1=C2 False C1=C2 假
C2=C1 False C2=C1 错误
C2=C3 True BUG? C2=C3 真正的BUG?
C3=C2 True BUG? C3=C2 真正的BUG?

However MSDN Documentation recommdends:但是MSDN 文档建议:

Notes to Implementers If you implement Equals, you should also override the base class implementations of Object.Equals(Object) and GetHashCode so that their behavior is consistent with that of the IEquatable<T>.Equals method.对实现者的说明 如果您实现 Equals,您还应该覆盖 Object.Equals(Object) 和 GetHashCode 的基类实现,以便它们的行为与 IEquatable<T>.Equals 方法的行为一致。 If you do override Object.Equals(Object), your overridden implementation is also called in calls to the static Equals(System.Object, System.Object) method on your class.如果您确实覆盖了 Object.Equals(Object),则您的覆盖实现也会在对类上的静态 Equals(System.Object, System.Object) 方法的调用中调用。 In addition, you should overload the op_Equality and op_Inequality operators.此外,您应该重载 op_Equality 和 op_Inequality 运算符。 This ensures that all tests for equality return consistent results, which the example illustrates.这确保了所有相等性测试都返回一致的结果,该示例说明了这一点。

Starting from this moment I see no reason to implement IEquatable<T> for reference types.从这一刻开始,我认为没有理由为引用类型实现IEquatable<T> Can anyone tell me when it has any sense?谁能告诉我什么时候有意义? Should I really treat different equality behavior as inconsistent when we look at the type differently (as base type)?当我们以不同的方式看待类型(作为基类型)时,我真的应该将不同的相等行为视为不一致吗?

Today I asked myself which consequences arise when adding IEquatable<T> to a class, and I found your question.今天我问自己将IEquatable<T>添加到类时会出现什么后果,我发现了你的问题。
Then I tested your code.然后我测试了你的代码。 For everyone else reading this, here's an answer, instead of only "just do it like that to make it work".对于阅读本文的其他人,这里有一个答案,而不仅仅是“这样做才能使其发挥作用”。

First of all, it's not a bug.首先,这不是一个错误。
Your problem is, that you specify an EqualityComparer<Class1> , which is only implemented in class1 by public bool Equals(Class1 other) .您的问题是,您指定了一个EqualityComparer<Class1> ,它仅在class1public bool Equals(Class1 other)
Therefore, dec1.Equals(c2, c3) will call this function where only the content of class1 is compared.因此, dec1.Equals(c2, c3)将调用此函数,其中仅比较class1的内容。

From your comment BUG?从你的评论BUG? I can see that you expect the content of class2 to be compared as well, just like everybody else would expect, too.我可以看到您也希望对class2的内容进行比较,就像其他人所期望的一样。 To achieve this, you need to change为了实现这一点,你需要改变
public bool Equals(Class1 other)
into进入
public virtual bool Equals(Class1 other)
and override this function in class2 , where you then also compare the content of class2 .并在class2覆盖此函数,然后您还可以比较class2的内容。
But that may lead to a quite weird construct.但这可能会导致一个非常奇怪的结构。 Therefore, for completeness, here's my way of implementation:因此,为了完整起见,这是我的实现方式:

In the base class , only type checks:基类中,只进行类型检查:

//--------------------------------------------------------------------------
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);
}

Still in the base class , content checks:仍然在基类中,内容检查:

///-------------------------------------------------------------------------
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
      && ... ;
}

In the derived classes , content checks:派生类中,内容检查:

///-------------------------------------------------------------------------
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
      && ... ;
}

Rightly or wrongly, here is how I have tended to implement Equals(Object) and IEquatable<T>.Equals(T) on base and derived classes.正确或错误,这是我倾向于在基类和派生类上实现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
    }
}

For reference types, the benefits of implementating IEquatable<T> are marginal, if you have two instances of type T , you are able to directly invoke T.Equals(T) .对于引用类型,实现IEquatable<T>的好处是微不足道的,如果您有两个T类型的实例,您可以直接调用T.Equals(T) instead of T.Equals(Object) which subsequently requires type checking to be performed on the parameter.而不是T.Equals(Object)随后需要对参数执行类型检查。

The primary purpose of IEquatable<T> is for value types, where there is overhead in boxing the instance. IEquatable<T>的主要目的是用于值类型,其中装箱实例有开销。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM