程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> Effective C#原則10:明白GetHashCode()的缺陷(1)

Effective C#原則10:明白GetHashCode()的缺陷(1)

編輯:關於C語言

這是本書中唯一一個被一整個函數占用的原則,你應該避免寫這樣的函數。 GetHashCode()僅在一種情況下使用:那就是對象被用於基於散列的集合的關鍵 詞,如經典的HashTable或者Dictionary容器。這很不錯,由於在基類上實現的 GetHashCode()存在大量的問題。對於引用類型,它可以工作,但高效不高;對 於值類型,基類的實現經常出錯。這更糟糕。你自己完全可以寫一個即高效又正 確的GetHashCode()。沒有那個單一的函數比GetHashCode()討論的更多,且令人 困惑。往下看,為你解釋困惑。

如果你定義了一個類型,而且你決不准 備把它用於某個容器的關鍵詞,那就沒什麼事了。像窗體控件,網頁控件,或者 數據庫鏈接這樣的類型是不怎像要做為某個任何的關鍵詞的。在這些情況下,什 麼都不用做了。所有的引用類型都會得到一個正確的散列值,即使這樣效率很糟 糕。值類型應該是恆定的(參見原則7),這種情況下,默認的實現總是工作的, 盡管這樣的效率也是很糟糕的。在大多數情況下,你最好完全避免在類型的實例 上使用GetHashCode()。

然而,在某天你創建了一個要做為HashTable的 關鍵詞來使用的類型,那麼你就須要重寫你自己的GetHashCode()的實現了。繼 續看,基於散列(算法)的集合用散列值來優化查找。每一個對象產生一個整型的 散列值,而該對象就存儲在基於這個散列值的“桶”中。為了查找某 個對象,你通過它的散列值來找到這個(存儲了實際對象的)“桶”。 在.Net裡,每一對象都有一個散列值,它是由System.Object.GetHashCode()斷 定的。任何對GetHashCode()的重寫都必須遵守下面的三個規則:

1、如 果兩個對象是相等的(由操作符==所定義),那麼它們必須產生相同的散列值。否 則,無法通過散列值在容器中找到對象。

2、對於任意對象A, A.GetHashCode()必須是實例不變的。不管在A上調用了什麼方法, A.GetHashCode()必須總是返回同樣的散列值。這就保證在某個“桶 ”中的對象始終是在這個“桶”中。

3、對於任意的輸 入,散列函數總是產生產生一個介於整型內的隨機分布。這會讓你在一個基於散 列的容器取得好的效率。

為一個類型寫一個正確且高效的散列函數來滿 足上面的三條,要對該類型有廣泛的認識。System.Object和System.ValueType 的默認版本並不具備什麼優勢。這些版本必須為你的特殊類型提供默認的行為, 而同時它們對這些特殊的類型又並不了解。Object.GetHashCode()是使用 System.Object內在的成員來產生散列值。每個對象在產生時指定一個唯一的值 來做為對象關鍵詞,(這個值)以整型來存儲。這些關鍵詞從1開始,在每次有任 何新的對象產生時逐漸增加。對象的ID字段在System.Object的構造函數進行設 置,並且今後再也不能修改。Object.GetHashCode()就是把這個值當成給定對象 的散列值來返回。

(譯注:注意這裡說的是默認的引用類型,其它情況就 不是這樣的了。)

現在我們根據上面的三個原則來驗證 Object.GetHashCode()。如果兩個對象是相等的,Object.GetHashCode()返回同 樣的散列值,除非你重寫了操作符==。System.Object這個版本的==操作符是檢 測對象的ID。GetHashCode()返回對象內部的ID字段,它是好的。然而,如果你 提供了自己的==版本,你就必須同時提供你自己版本的GetHashCode(),從而保 證遵守了前面說的第一條規則。相等的細節參見原則9。

第二條規則已經 遵守了:一個對象創建後,它的散列值是不能改變的。

第三條規則,對 所有的輸入,在整型內進行隨機分布,這並沒有被支持。這個數字序列並不是整 型上的隨機分布,除非你創建了大量的對象。Object.GetHashCode()所產生的散 列值主要集中在盡可能小的整型范圍內。

這就是說這個 Object.GetHashCode()是正確的,但並不高效。如果你在你定義的引用類型上創 建一個散列表,這個默認從System.Object上繼承的行為是工作的,但是比較慢 。當你創建准備用於散列關鍵詞的引用類型時,你應該為你的特殊類型重寫 GetHashCode(),從而提供更好的在整型范圍上隨機分布的散列值。

在講 解如何重寫你自己的GetHashCode()之前,這一節來驗證 ValueType.GetHashCode()是否也遵守上面的三條規則。System.ValueType重寫 了GetHashCode(),為所有的值類型提供默認的行為。這一版本從你所定義的類 型的第一個字段上返回散列。考慮這個例子:

public struct MyStruct
{
 private string  _msg;
 private int    _id;
 private DateTime _epoch;
}

從MyStruct對 象上返回的散列值是從該對象的_msg成員上生成的。下面的代碼片斷總是返回 true:

MyStruct s = new MyStruct( );
return s.GetHashCode( ) == s._msg.GetHashCode( );

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved