簡體   English   中英

C#-必須實現哪些接口+運算符才能獲得自定義類型的值比較和相等性?

[英]C# - What interfaces + operators must be implemented to get value comparison and equality on custom types?

假設我有一個自定義類型,例如

public class MyType
{
    public string name;
    public int count;

    public MyType(string n, int c)
    {
        name = n;
        count = c;
    }
}

在C#中,希望對該對象的實例進行“直觀”的相等比較。 那是按值比較而不是參考。 我知道相等運算符==Object.Equals()默認引用相等。 但是如果內容匹配,我希望MyType兩個對象相等。 第一步是使用類似以下方法覆蓋Object.Equals()operator==

public override bool Equals(object obj)
{
   MyType t = obj as MyType;
   return (this.name == t.name) && (this.count == t.count);
}

但是,也有類似的接口:

似乎可用於涉及比較的各種情況。

我是否必須實現所有這些接口以及operator==operator!=以確保涉及MyType任何比較(包括在諸如List<T>.Contains()這樣的通用集合中使用)的比較都是按值而不是按值進行比較參考? 還是我想念其他方式? 在我看來,要實現諸如值比較之類的簡單操作,有七個接口和兩個運算符很多。

無需實現那些接口即可實現相等運算符。 只需根據類型重寫它們即可。

但是,如果這樣做,則必須重寫Equals(object) GetHashCode()以確保行為正確。 應該實現IEquatable<T>因為...可能也是如此。

同樣,為了進行比較,請根據需要覆蓋運算符。 它們沒有必需的接口。 但是,如果這樣做,則應該再次以與運算符的行為一致的方式實現IComparable<T>

如果您需要在實現對象更改對象的比較方式,則只有IEqualityComparerIComparer才需要實現。

您只需要IEquatable<T>以及兩個運算符。 至於其他:

如果要創建另一個可以確定其他對象是否相等的對象,則使用IEqualityComparerIEqualityComparer<T>

如果您希望能夠對項目進行排序,則可以使用IComparerIComparer<T> (這樣就可以確定一個項目是否大於另一個項目,不一定等於相等項)。

編輯:

就像Je​​ff提到的那樣,如果覆蓋Equals ,則還應該覆蓋GetHashCode()

第一步是使用類似以下方法覆蓋Object.Equals()operator==

不,第一步是重寫object.Equals()GetHashCode() 您絕不能覆蓋一個而不覆蓋另一個對應的內容,否則如果用作鍵,則您的類是錯誤的。

讓我們看看您的Equals()

public override bool Equals(object obj)
{
   MyType t = obj as MyType;
   return (this.name == t.name) && (this.count == t.count);
}

這里有一個錯誤,因為如果obj為null或不是MyType它將拋出NullReferenceException 讓我們修復一下:

public override bool Equals(object obj)
{
   MyType t = obj as MyType;
   return t != null && (name == t.name) && (count == t.count);
}

我也可能將count比較放在name之前,因為如果不匹配, count比較可能會更快,但是我不知道您的用例,因此也許有少數非常常見的count值,在這種情況下不成立。 不過,這是一個優化問題,讓我們通過給您一個對應的GetHashCode()修復該錯誤。

public override int GetHashCode()
{
   return (name?.GetHashCode() ?? 0) ^ count;
}

最低要求是,如果a.Equals(b)a.GetHashCode() == b.GetHashCode()必須為真。 理想情況下,我們還希望將位盡可能地擴展。 我們通過將哈希碼基於確定相等性的屬性來實現第一(重要)部分。 第二部分更加復雜,但是在這種情況下,字符串的GetHashCode()質量相對較高,這意味着僅將其余的整數值進行異或運算可能就可以了。 在網站上搜索更多詳細信息(包括為什么在其他情況下僅進行異化通常不是一個好主意)。

現在,您需要==語義。 這是一個要求,如果您定義== ,則必須定義!= ,但是我們可以輕松定義另一個。

public static bool operator !=(MyType x, MyType y)
{
    return !(x == y);
}

現在,一旦我們==完成!=將經歷該過程。 當然,我們已經定義了相等性,因此我們可以從使用它開始:

public static bool operator ==(MyType x, MyType y)
{
    return x.Equals(y);
}

盡管這是有問題的,因為當它處理y為null時,如果x為null則拋出該錯誤。 我們也需要考慮:

public static bool operator ==(MyType x, MyType y)
{
    if (x == null)
    {
         return y == null;
    }
    return x.Equals(y);
}

讓我們考慮一下,盡管所有事物都必須與自身相等(實際上,如果不成立的話,您將遇到許多麻煩)。 因為我們必須考慮x == null && y == null的可能性,所以我們以(object)x == (object)y的情況為例。 讓我們跳過其余測試:

public static bool operator ==(MyType x, MyType y)
{
    if ((object)x == (object)y)
    {
        return true;
    }
    if ((object)x == null)
    {
        return false;
    }
    return x.Equals(y);
}

這有多大的好處取決於與自我進行比較的可能性(作為各種事物的副作用比您可能猜到的更為普遍)和平等方法的昂貴程度(在這種情況下,費用不多,但一個具有更多字段進行比較的案例可能會相當可觀)。

好的,我們對EqualsGetHashCode進行了排序,並根據需要添加了==!= 什么會是不錯的就是IEqutable<MyType> 這提供了一個強類型的Equals ,當字典,哈希集等中的比較器可以使用它們時使用。因此非常高興。 這將迫使我們實現bool Equals(MyType other) ,這與我們已經進行的替代非常相似,但是沒有進行轉換:

public bool Equals(MyType other)
{
   return other != null && (name == other.name) && (count == other.count);
}

獎勵:由於重載的工作方式,我們的==將調用此稍微快一點的方法,而不是執行強制轉換的重寫。 我們優化了== ,並擴展了!= ,甚至沒有碰到它們!

現在,如果要實現此功能,則必須實現GetHashCode() ,這又意味着必須實現object.Equals()重寫,但是我們已經做到了。 不過,我們在這里重復,所以讓我們重寫重寫以使用強類型形式:

public override bool Equals(object obj)
{
  return Equals(obj as MyType);
}

全部做完。 把它放在一起:

public class MyType : IEquatable<MyType>
{
    public string name;
    public int count;

    public MyType(string n, int c)
    {
        name = n;
        count = c;
    }

    public bool Equals(MyType other)
    {
       return other != null && (name == other.name) && (count == other.count);
    }

    public override bool Equals(object obj) => Equals(obj as MyType);

    public override int GetHashCode() => (name?.GetHashCode() ?? 0) ^ count;

    public static bool operator ==(MyType x, MyType y)
    {
        if ((object)x == (object)y)
        {
            return true;
        }

        if ((object)x == null)
        {
            return false;
        }

        return x.Equals(y);
    }

    public static bool operator !=(MyType x, MyType y) => !(x == y);
}

如果您還希望能夠訂購對象,則使用IComparable<T>IComparable 說一個比另一個小或先於另一個。 平等並不需要它。

IEqualityComparer<T>IEqualityComparer用於覆蓋以上所有內容,並在另一類中以完全不同的方式定義相等性。 這里的經典示例是,有時我們希望“ abc”等於“ ABC”,有時卻不希望,因此我們不能僅僅依靠==或上面描述的類型的Equals()方法因為他們只能應用一條規則。 它們通常由其他類別提供給要比較的實際類別。

假設我們有時在比較MyType實例時想忽略大小寫。 然后我們可以做:

public class CaseInsensitiveMyTypeEqualityComparer : IEqualityComparer<MyType>
{
    public bool Equals(MyType x, MyType y)
    {
        if ((object)x == (object)y)
        {
            return true;
        }
        if ((object)x == null | (object)y == null)
        {
            return false;
        }
        return x.count == y.count && string.Equals(x.name, y.name, StringComparison.OrdinalIgnoreCase);
    }

    public int GetHashCode(MyType obj)
    {
        if (obj == null)
        {
            return 0;
        }
        return StringComparer.OrdinalIgnoreCase.GetHashCode(obj.name) ^ obj.count;
    }
}

如果您這樣說:

var dictionary = new Dictionar<MyType, int>(new CaseInsensitiveMyTypeEqualityComparer());

然后,字典對其鍵將不區分大小寫。 請注意,由於我們基於名稱的不區分大小寫的比較定義了相等性,因此我們也必須將哈希碼也基於不區分大小寫的哈希值。

如果您不使用IEqualityComparer<MyType>則字典將使用EqualityComparer<MyType>.Default ,因為它可以使用效率更高的IEquatable<MyType>實現,並且會使用該object.Equals有。

您可能會猜想IEqualityComparer<T>的使用相對較少,而不僅僅是使用類本身定義的相等性。 另外,如果某人確實需要它,那么該人可能不是您。 最好的事情之一就是我們可以為其他人的班級定義它們。 不過,您不必為自己的課程設計而擔心。

暫無
暫無

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

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