![](/img/trans.png)
[英]Converting c# to vb.net. What to do with comparison operators and Interfaces?
[英]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);
}
但是,也有類似的接口:
IEquatable<T>
IEqualityComparer
IEqualityComparer<T>
IComparer
IComparer<T>
IComparable
IComparable<T>
似乎可用於涉及比較的各種情況。
我是否必須實現所有這些接口以及operator==
和operator!=
以確保涉及MyType
任何比較(包括在諸如List<T>.Contains()
這樣的通用集合中使用)的比較都是按值而不是按值進行比較參考? 還是我想念其他方式? 在我看來,要實現諸如值比較之類的簡單操作,有七個接口和兩個運算符很多。
您無需實現那些接口即可實現相等運算符。 只需根據類型重寫它們即可。
但是,如果這樣做,則必須重寫Equals(object)
和 GetHashCode()
以確保行為正確。 您應該實現IEquatable<T>
因為...可能也是如此。
同樣,為了進行比較,請根據需要覆蓋運算符。 它們沒有必需的接口。 但是,如果這樣做,則應該再次以與運算符的行為一致的方式實現IComparable<T>
。
如果您需要在實現對象后更改對象的比較方式,則只有IEqualityComparer
或IComparer
才需要實現。
您只需要IEquatable<T>
以及兩個運算符。 至於其他:
如果要創建另一個可以確定其他對象是否相等的對象,則使用IEqualityComparer
和IEqualityComparer<T>
。
如果您希望能夠對項目進行排序,則可以使用IComparer
和IComparer<T>
(這樣就可以確定一個項目是否大於另一個項目,不一定等於相等項)。
編輯:
就像Jeff提到的那樣,如果覆蓋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);
}
這有多大的好處取決於與自我進行比較的可能性(作為各種事物的副作用比您可能猜到的更為普遍)和平等方法的昂貴程度(在這種情況下,費用不多,但一個具有更多字段進行比較的案例可能會相當可觀)。
好的,我們對Equals
和GetHashCode
進行了排序,並根據需要添加了==
和!=
。 什么會是不錯的就是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.