簡體   English   中英

關於如何正確覆蓋object.GetHashCode()的一般建議和指南

[英]General advice and guidelines on how to properly override object.GetHashCode()

根據MSDN ,散列函數必須具有以下屬性:

  1. 如果兩個對象比較相等,則每個對象的GetHashCode方法必須返回相同的值。 但是,如果兩個對象的比較不相等,則兩個對象的GetHashCode方法不必返回不同的值。

  2. 只要沒有對對象狀態的修改來確定對象的Equals方法的返回值,對象的GetHashCode方法必須始終返回相同的哈希代碼。 請注意,這僅適用於當前應用程序的執行,並且如果再次運行應用程序,則可以返回不同的哈希代碼。

  3. 為獲得最佳性能,哈希函數必須為所有輸入生成隨機分布。


我一直在以下場景中找到自己:我創建了一個類,實現了IEquatable<T>並重寫object.Equals(object) IEquatable<T> object.Equals(object) MSDN聲明:

重寫Equals的類型也必須覆蓋GetHashCode; 否則,Hashtable可能無法正常工作。

然后它通常會為我停止一點。 因為,你如何正確覆蓋object.GetHashCode() 從來沒有真正知道從哪里開始,這似乎是很多陷阱。

在StackOverflow中,有很多與GetHashCode重寫相關的問題,但大多數問題似乎都是針對非常特殊的情況和具體問題。 因此,我想在這里得到一個很好的匯編。 概述與一般建議和指南。 該做什么,不該做什么,常見的陷阱,從哪里開始,等等。

我希望它特別針對C#,但我認為它對其他.NET語言也有同樣的作用(?)。


我想也許最好的方法是每個主題創建一個答案,首先是快速簡短的答案(如果可能的話,盡可能接近單行),然后可能會有更多信息,並以相關問題,討論,博客文章等結束。 ,如果有的話。 然后,我可以創建一個帖子作為接受的答案(將其置於頂部),只需一個“目錄”。 盡量保持簡潔明了。 而且不要只鏈接到其他問題和博客文章。 嘗試采用它們的本質,然后鏈接到源(特別是因為源可能會消失。另外,請嘗試編輯和改進答案,而不是創建許多非常相似的答案。

我不是一個非常優秀的技術作家,但我至少會嘗試格式化答案,使它們看起來很相似,創建目錄等。我也會嘗試在這里搜索一些相關的問題來回答部分問題。這些並且可能拉出我能管理的那些的本質。 但由於我在這個主題上不是很穩定,所以我會盡量遠離這個主題:p

目錄


我希望涵蓋的內容,但尚未完成:

  • 如何創建整數(如何將對象“轉換”為int對我來說不是很明顯)。
  • 基於哈希代碼的字段。
    • 如果它只應該在不可變字段上,那么如果只有可變字段呢?
  • 如何生成一個好的隨機分布。 (MSDN Property#3)
    • 在這方面,似乎選擇了一個很好的魔術素數(已經看過使用了17,23和397),但是你如何選擇它,它究竟是什么呢?
  • 如何確保哈希代碼在整個對象生存期內保持不變。 (MSDN Property#2)
    • 特別是當相等性基於可變字段時。 (MSDN Property#1)
  • 如何處理復雜類型的字段(不在內置的C#類型中 )。
    • 復雜對象和結構,數組,集合,列表,字典,泛型類型等。
    • 例如,即使列表或字典可能只讀,但這並不意味着它的內容。
  • 如何處理繼承的類。
    • 你應該以某種方式將base.GetHashCode()合並到你的哈希代碼中嗎?
  • 你在技術上可能只是懶惰並返回0嗎? 將嚴重破壞MSDN准則號#3,但至少會確保#1和#2始終為真:P
  • 常見的陷阱和陷阱。

在GetHashCode實現中常見的那些神奇數字是什么?

他們是素數。 素數用於創建哈希碼,因為素數最大化了哈希碼空間的使用。

具體來說,從小素數3開始,只考慮結果的低階nybbles

  • 3 * 1 = 3 = 3(mod 8)= 0011
  • 3 * 2 = 6 = 6(mod 8)= 1010
  • 3 * 3 = 9 = 1(mod 8)= 0001
  • 3 * 4 = 12 = 4(mod 8)= 1000
  • 3 * 5 = 15 = 7(mod 8)= 1111
  • 3 * 6 = 18 = 2(mod 8)= 0010
  • 3 * 7 = 21 = 5(mod 8)= 1001
  • 3 * 8 = 24 = 0(mod 8)= 0000
  • 3 * 9 = 27 = 3(mod 8)= 0011

我們重新開始。 但是你會注意到,在開始重復之前,我們的素數的連續倍數在我們的nybble中生成了每個可能的位排列。 我們可以使用任何素數和任意數量的位獲得相同的效果,這使得素數最適合生成近似隨機哈希碼。 我們通常在上面的例子中看到較大的素數而不是像3這樣的小素數的原因是,對於哈希碼中更大的比特數,使用小素數得到的結果甚至不是偽隨機的 - 它們只是一個增加序列直到遇到溢出。 為了獲得最佳隨機性,應使用導致相當小系數溢出的素數,除非您可以保證系數不會很小。

相關鏈接:

查看Eric Lippert的GetHashCode指南和規則

只要對該類型的對象有一個有意義的相等度量(即重寫等於),就應該覆蓋它。 如果你知道對象不會因為任何原因而被刪除,你可以離開它,但你不可能提前知道這一點。

哈希應該僅基於用於定義相等性的對象的屬性,因為被認為相等的兩個對象應該具有相同的哈希碼。 一般來說,你通常會這樣做:


public override int GetHashCode()
{
    int mc = //magic constant, usually some prime
    return mc * prop1.GetHashCode() * prop2.GetHashCode * ... * propN.GetHashCode();
}

我通常假設將值相乘將產生相當均勻的分布,假設每個屬性的哈希碼函數都是相同的,盡管這可能是錯誤的。 使用此方法,如果對象的相等定義屬性發生更改,則哈希代碼也可能會更改,這在您的問題中定義#2時是可接受的。 它還以統一的方式處理所有類型。

您可以為所有實例返回相同的值,但這會使任何使用散列的算法(例如dictionarys)非常慢 - 基本上所有實例都將被散列到同一個桶,然后查找將變為O(n)而不是預期O(1)。 這當然否定了使用這種結構進行查找的任何好處。

為什么我必須覆蓋object.GetHashCode()

覆蓋此方法很重要,因為以下屬性必須始終保持為true:

如果兩個對象比較相等,則每個對象的GetHashCode方法必須返回相同的值。

正如JaredPar在關於實現平等的博客文章中所說的那樣,原因在於

許多類使用哈希代碼對對象進行分類。 特別是哈希表和字典傾向於根據哈希代碼將對象放在存儲桶中。 當檢查對象是否已經在哈希表中時,它將首先在桶中查找它。 如果兩個對象相等但具有不同的哈希碼,則它們可能被放入不同的桶中,並且字典將無法查找該對象。

相關鏈接:

A)如果要使用值相等而不是默認引用相等,則必須覆蓋Equals和GetHashCode。 對於后者,如果它們都引用相同的對象實例,則兩個對象引用相等。 如果它們的值相同,即使它們引用不同的對象,它們與前者相比也是相等的。 例如,您可能希望為Date,Money和Point對象使用值相等。

B)為了實現值相等,您必須重寫Equals和GetHashCode。 兩者都應該取決於封裝該值的對象的字段。 例如,Date.Year,Date.Month和Date.Day; 或Money.Currency和Money.Amount; 或Point.X,Point.Y和Point.Z。 您還應該考慮重寫operator ==,operator!=,operator <和operator>。

C)哈希碼不必在整個對象生存期內保持不變。 但是,當它作為哈希中的鍵參與時,它必須保持不可變。 從MSDN doco for Dictionary:“只要一個對象被用作Dictionary <(Of <(TKey,TValue>)>)中的一個鍵,它就不能以任何影響其哈希值的方式改變。” 如果必須更改密鑰的值,請從字典中刪除條目,更改密鑰值,然后替換該條目。

D)IMO,如果你的價值對象本身是不可變的,你將簡化你的生活。

我什么時候覆蓋object.GetHashCode()

正如MSDN所述:

重寫Equals的類型也必須覆蓋GetHashCode; 否則,Hashtable可能無法正常工作。

相關鏈接:

基於哈希碼的字段是什么? 如果它只應該在不可變字段上,那么如果只有可變字段呢?

它不需要僅基於不可變字段。 我將它基於確定equals方法結果的字段。

如何確保哈希代碼在整個對象生存期內保持不變。 (MSDN屬性#2)特別是當相等性基於可變字段時。 (MSDN Property#1)

你似乎誤解了物業#2。 在對象生存期內,哈希碼不需要保持不變。 只要確定equals方法結果的值不變,它就需要保持不變。 因此,邏輯上,您只將哈希碼基於這些值。 那應該不會有問題。

public override int GetHashCode()
{
    return IntProp1 ^ IntProp2 ^ StrProp3.GetHashCode() ^ StrProp4.GetHashCode ^ CustomClassProp.GetHashCode;
}

在customClass的GetHasCode方法中執行相同的GetHasCode 奇跡般有效。

暫無
暫無

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

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