![](/img/trans.png)
[英]Why are you expected to override GetHashCode and Equals when overloading the equality operator?
[英]Using GetHashCode to test equality in Equals override
是否可以調用 GetHashCode 作為從 Equals 覆蓋內部測試相等性的方法?
例如,這個代碼可以接受嗎?
public class Class1
{
public string A
{
get;
set;
}
public string B
{
get;
set;
}
public override bool Equals(object obj)
{
Class1 other = obj as Class1;
return other != null && other.GetHashCode() == this.GetHashCode();
}
public override int GetHashCode()
{
int result = 0;
result = (result ^ 397) ^ (A == null ? 0 : A.GetHashCode());
result = (result ^ 397) ^ (B == null ? 0 : B.GetHashCode());
return result;
}
}
其他人是對的; 你的平等操作被打破了。 為了顯示:
public static void Main()
{
var c1 = new Class1() { A = "apahaa", B = null };
var c2 = new Class1() { A = "abacaz", B = null };
Console.WriteLine(c1.Equals(c2));
}
我想您希望該程序的輸出為“假”,但根據您對相等性的定義,它在 CLR 的某些實現中為“真”。
請記住,只有大約 40 億個可能的哈希碼。 有超過 40 億個可能的六個字母字符串,因此其中至少有兩個具有相同的哈希碼。 我給你們看了兩個; 還有無限多的。
一般來說,您可以預期,如果有 n 個可能的哈希碼,那么一旦您擁有 n 個元素的平方根,發生沖突的幾率就會急劇上升。 這就是所謂的“生日悖論”。 對於我關於為什么不應該依賴哈希碼進行相等性的文章,請參閱:
http://blogs.msdn.com/b/ericlippert/archive/2010/03/22/socks-birthdays-and-hash-collisions.aspx
不,這不行,因為它不是
equality <=> hashcode equality
。
這只是
equality => hashcode equality
。
或在另一個方向:
hashcode inequality => inequality
。
引用http://msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx :
如果兩個對象比較相等,則每個對象的 GetHashCode 方法必須返回相同的值。 但是,如果兩個對象不相等,則兩個對象的 GetHashCode 方法不必返回不同的值。
我會說,除非您希望Equals
基本上意味着“與您的類型具有相同的哈希碼”,否則沒有,因為兩個字符串可能不同但共享相同的哈希碼。 概率可能很小,但不是零。
不,這不是測試相等性的可接受方式。 2 個不相等的值很可能具有相同的哈希碼。 這將導致您的Equals
true
在應該返回false
時返回false
你可以調用GetHashCode
確定的項目是不相等的,但如果兩個對象返回相同的散列碼,這並不意味着他們是平等的。 兩個項目可以具有相同的哈希碼,但不能相等。
如果比較兩個項目的成本很高,那么您可以比較哈希碼。 如果他們不平等,那么你可以保釋。 否則(哈希碼相等),您必須進行完整比較。
例如:
public override bool Equals(object obj)
{
Class1 other = obj as Class1;
if (other == null || other.GetHashCode() != this.GetHashCode())
return false;
// the hash codes are the same so you have to do a full object compare.
}
您不能僅僅因為哈希碼相等就說對象必須相等。
在Equals
調用GetHashCode
的唯一時間是計算對象的哈希值(例如,因為您緩存它)比檢查相等性要便宜得多。 在這種情況下,您可以說if (this.GetHashCode() != other.GetHashCode()) return false;
以便您可以快速驗證對象是否不相等。
那么你什么時候會這樣做呢? 我編寫了一些代碼,它定期截取屏幕截圖,並嘗試找出屏幕更改后的時間。 由於我的屏幕截圖大小為 8MB,並且在屏幕截圖間隔內變化的像素相對較少,因此搜索它們的列表以查找相同的像素是相當昂貴的。 哈希值很小,每個屏幕截圖只需計算一次,可以輕松消除已知的不相等值。 事實上,在我的應用程序中,我認為具有相同的哈希值足夠接近相等,以至於我什至沒有費心實現Equals
重載,導致 C# 編譯器警告我我正在重載GetHashCode
而不重載Equals
。
這是錯誤的實現,正如其他人所說的那樣。
您應該使用GetHashCode
短路相等性檢查,例如:
if (other.GetHashCode() != this.GetHashCode() return false;
在Equals
方法中,只有當您確定隨后的 Equals 實現比GetHashCode
昂貴得多時,這不是絕大多數情況。
在您展示的這個實現中(占 99% 的情況)它不僅壞了,而且速度也慢得多。 原因是什么? 計算屬性的散列幾乎肯定會比比較它們慢,因此您甚至沒有在性能方面獲得收益。 實現正確的GetHashCode
的優點是當您的類可以作為哈希表的鍵類型時,哈希只計算一次(並且該值用於比較)。 在您的情況下,如果GetHashCode
在一個集合中,它將被多次調用。 盡管GetHashCode
本身應該很快,但它並不比等效的Equals
快。
要進行基准測試,請在此處運行您的Equals
(一個正確的實現,取出當前基於哈希的實現)和GetHashCode
var watch = Stopwatch.StartNew(); for (int i = 0; i < 100000; i++) { action(); //Equals and GetHashCode called here to test for performance. } watch.Stop(); Console.WriteLine(watch.Elapsed.TotalMilliseconds);
在一種情況下,使用哈希碼作為等式比較的捷徑是有意義的。
考慮您正在構建哈希表或哈希集的情況。 事實上,讓我們只考慮散列集(散列表通過還持有一個值來擴展它,但這並不相關)。
可以采用各種不同的方法,但在所有這些方法中,您都有少量可以放置散列值的槽,我們采用開放或封閉的方法(這只是為了好玩,有些人使用相反的行話給別人); 如果我們在同一個槽上碰撞兩個不同的對象,我們可以將它們存儲在同一個槽中(但有一個鏈表或諸如此類用於實際存儲對象的位置)或通過重新探測選擇不同的槽(有各種為此的策略)。
現在,無論采用哪種方法,我們都將使用哈希表從我們想要的 O(1) 復雜度轉向 O(n) 復雜度。 這樣做的風險與可用槽的數量成反比,因此在達到一定大小后我們調整哈希表的大小(即使一切都很理想,如果存儲的項目數量大於數量,我們最終必須這樣做插槽)。
在調整大小時重新插入項目顯然取決於哈希碼。 正因為如此,雖然在對象中記住GetHashCode()
很少有意義(它只是在大多數對象上調用得不夠頻繁),但在哈希表本身中記住它肯定是有意義的(或者也許,記住生成的結果,例如如果您使用 Wang/Jenkins 哈希重新哈希以減少由錯誤的GetHashCode()
實現造成的損害。
現在,當我們開始插入時,我們的邏輯將是這樣的:
因此,在這種情況下,我們必須在比較相等性之前獲取哈希碼。 我們也已經預先計算了現有對象的哈希碼以允許調整大小。 這兩個事實的結合意味着對第 4 項進行比較是有意義的:
private bool IsMatch(KeyType newItem, KeyType storedItem, int newHash, int oldHash)
{
return ReferenceEquals(newItem, storedItem) // fast, false negatives, no false positives (only applicable to reference types)
||
(
newHash == oldHash // fast, false positives, no fast negatives
&&
_cmp.Equals(newItem, storedItem) // slow for some types, but always correct result.
);
}
顯然,這樣做的優勢取決於_cmp.Equals
的復雜性。 如果我們的鍵類型是int
那么這將完全是一種浪費。 如果我們的鍵類型 where string 和我們使用不區分大小寫的 Unicode 規范化相等比較(因此它甚至不能縮短長度),那么節省很值得。
通常記住哈希碼沒有意義,因為它們的使用頻率不夠高,不足以提高性能,但將它們存儲在哈希集或哈希表本身中是有意義的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.