簡體   English   中英

平等比較者<T> .Default.Equals() 與 object.Equals() 和多態

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

再次討論平等我偶然發現了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<Class1>.Default.Equals
C1=C2 假
C2=C1 錯誤
C2=C3 真正的BUG?
C3=C2 真正的BUG?

但是MSDN 文檔建議:

對實現者的說明 如果您實現 Equals,您還應該覆蓋 Object.Equals(Object) 和 GetHashCode 的基類實現,以便它們的行為與 IEquatable<T>.Equals 方法的行為一致。 如果您確實覆蓋了 Object.Equals(Object),則您的覆蓋實現也會在對類上的靜態 Equals(System.Object, System.Object) 方法的調用中調用。 此外,您應該重載 op_Equality 和 op_Inequality 運算符。 這確保了所有相等性測試都返回一致的結果,該示例說明了這一點。

從這一刻開始,我認為沒有理由為引用類型實現IEquatable<T> 誰能告訴我什么時候有意義? 當我們以不同的方式看待類型(作為基類型)時,我真的應該將不同的相等行為視為不一致嗎?

今天我問自己將IEquatable<T>添加到類時會出現什么后果,我發現了你的問題。
然后我測試了你的代碼。 對於閱讀本文的其他人,這里有一個答案,而不僅僅是“這樣做才能使其發揮作用”。

首先,這不是一個錯誤。
您的問題是,您指定了一個EqualityComparer<Class1> ,它僅在class1public 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
      && ... ;
}

正確或錯誤,這是我傾向於在基類和派生類上實現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>的主要目的是用於值類型,其中裝箱實例有開銷。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM