[英]General advice and guidelines on how to properly override object.GetHashCode()
根據MSDN ,散列函數必須具有以下屬性:
如果兩個對象比較相等,則每個對象的GetHashCode方法必須返回相同的值。 但是,如果兩個對象的比較不相等,則兩個對象的GetHashCode方法不必返回不同的值。
只要沒有對對象狀態的修改來確定對象的Equals方法的返回值,對象的GetHashCode方法必須始終返回相同的哈希代碼。 請注意,這僅適用於當前應用程序的執行,並且如果再次運行應用程序,則可以返回不同的哈希代碼。
為獲得最佳性能,哈希函數必須為所有輸入生成隨機分布。
我一直在以下場景中找到自己:我創建了一個類,實現了IEquatable<T>
並重寫object.Equals(object)
IEquatable<T>
object.Equals(object)
。 MSDN聲明:
重寫Equals的類型也必須覆蓋GetHashCode; 否則,Hashtable可能無法正常工作。
然后它通常會為我停止一點。 因為,你如何正確覆蓋object.GetHashCode()
? 從來沒有真正知道從哪里開始,這似乎是很多陷阱。
在StackOverflow中,有很多與GetHashCode重寫相關的問題,但大多數問題似乎都是針對非常特殊的情況和具體問題。 因此,我想在這里得到一個很好的匯編。 概述與一般建議和指南。 該做什么,不該做什么,常見的陷阱,從哪里開始,等等。
我希望它特別針對C#,但我認為它對其他.NET語言也有同樣的作用(?)。
我想也許最好的方法是每個主題創建一個答案,首先是快速簡短的答案(如果可能的話,盡可能接近單行),然后可能會有更多信息,並以相關問題,討論,博客文章等結束。 ,如果有的話。 然后,我可以創建一個帖子作為接受的答案(將其置於頂部),只需一個“目錄”。 盡量保持簡潔明了。 而且不要只鏈接到其他問題和博客文章。 嘗試采用它們的本質,然后鏈接到源(特別是因為源可能會消失。另外,請嘗試編輯和改進答案,而不是創建許多非常相似的答案。
我不是一個非常優秀的技術作家,但我至少會嘗試格式化答案,使它們看起來很相似,創建目錄等。我也會嘗試在這里搜索一些相關的問題來回答部分問題。這些並且可能拉出我能管理的那些的本質。 但由於我在這個主題上不是很穩定,所以我會盡量遠離這個主題:p
我希望涵蓋的內容,但尚未完成:
base.GetHashCode()
合並到你的哈希代碼中嗎? 他們是素數。 素數用於創建哈希碼,因為素數最大化了哈希碼空間的使用。
具體來說,從小素數3開始,只考慮結果的低階nybbles :
0011
1010
0001
1000
1111
0010
1001
0000
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)。 這當然否定了使用這種結構進行查找的任何好處。
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,如果你的價值對象本身是不可變的,你將簡化你的生活。
Visual Studio 2017
https://docs.microsoft.com/en-us/visualstudio/ide/reference/generate-equals-gethashcode-methods?view=vs-2017
ReSharper的
https://www.jetbrains.com/help/resharper/Code_Generation__Equality_Members.html
它不需要僅基於不可變字段。 我將它基於確定equals方法結果的字段。
你似乎誤解了物業#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.