GetHashCode方法引入的緣由
用大神Jeffrey Richter的話說,FCL的設計者認為,如果能將任何對象的任何實例放到一個哈希表集合中,會帶來很多好處。為此,System.Object提供了虛方法GetHashCode,他能獲取任意對象的Int32哈希碼。我想,這也是GetHashCode方法當時引入的緣由。
Object.GetHashCode方法的實現
我們在.NET Framework4.0平台下進行測試,從上面的“任何對象的任何實例”這句話我們隱約可以猜測,Object.GetHashCode方法應該是具有相同引用的兩個對象實例有相同的HashCode,如果兩個對象實例的值完全相同,但是不是指向同一個引用,.NET應該不能保證這兩個對象具有相同的HashCode,其實對以上結論驗證的最簡單的方法就是看一下微軟實現的源代碼,但是可惜的是微軟在Object類下面是這樣寫的:
public virtual int GetHashCode() { return RuntimeHelpers.GetHashCode(this); }
在RuntimeHelpers里並沒有什么實現:
[System.Security.SecuritySafeCritical] // auto-generated [ResourceExposure(ResourceScope.None)] [MethodImplAttribute(MethodImplOptions.InternalCall)] public new static extern bool Equals(Object o1, Object o2);
但是,我們可以寫代碼進行測試,我們自定義一個類HashCodeObj
class HashCodeObj { internal Int32 value1 = 0; internal Int32 value2 = 0; internal String str1 = "test"; }
然后進行如下處理
class Program { static void Main(string[] args) { HashCodeObj obj1 = new HashCodeObj(); obj1.value1 = 11; obj1.value2 = 22; obj1.str1 = "str1"; HashCodeObj obj2 = new HashCodeObj(); obj2.value1 = 11; obj2.value2 = 22; obj2.str1 = "str1"; //Int32 intHashCode1 = 11; //Int32 intHashCode2 = 11; Console.WriteLine("obj1 hashCode: " + obj1.GetHashCode()); //obj1.value1 = 22; Console.WriteLine("obj2 hashCode: " + obj2.GetHashCode()); //Console.WriteLine("intHashCode1 hashCode: " + intHashCode1.GetHashCode()); //Console.WriteLine("intHashCode2 hashCode: " + intHashCode2.GetHashCode()); Console.ReadKey(); }
obj1和obj2是兩個不同的實例,但是值完全相同,但是結果兩個實例的hashCode不同
然后我們在第一次打印出obj1的hashCode值后對其value1值進行修改,然后再次獲取obj1的hashCode,代碼如下:
class Program { static void Main(string[] args) { HashCodeObj obj1 = new HashCodeObj(); obj1.value1 = 11; obj1.value2 = 22; obj1.str1 = "str1"; //HashCodeObj obj2 = new HashCodeObj(); //obj2.value1 = 11; //obj2.value2 = 22; //obj2.str1 = "str1"; //Int32 intHashCode1 = 11; //Int32 intHashCode2 = 11; Console.WriteLine("obj1 hashCode: " + obj1.GetHashCode()); obj1.value1 = 22; Console.WriteLine("obj1 hashCode: " + obj1.GetHashCode()); //Console.WriteLine("intHashCode1 hashCode: " + intHashCode1.GetHashCode()); //Console.WriteLine("intHashCode2 hashCode: " + intHashCode2.GetHashCode()); Console.ReadKey(); }
最后打印的結果顯示hashCode是相同的
測試結果
從上面的例子我們可以推測,雖然我們無法獲取Object.GetHashCode方法的實現代碼,但是我們可以知道Object.GetHashCode方法是根據當前對象實例的地址來計算的。(不知道Object.GetHashCode方法的實現對沒有任何影響,我們只知道結果就可以了)。
Jeffrey Richter在他們著作中曾寫過:System.Object實現的GetHashCode方法對其派生類型以及類型中的字段一無所知。現在體會一下這句話還是很有道理的。
進一步的體會
雖然我們知道了Object.GetHashCode的規則,但是msdn上的一段話還是非常值得思考的,我直接就用中文將這段話寫到下面(需要看英文的地址:https://msdn.microsoft.com/zh-cn/library/11tbk3h9%28v=vs.100%29.aspx?f=255&MSPPError=-2147217396)
Object.GetHashCode 和 RuntimeHelpers.GetHashCode 方法用於以下情況: 1. Object.GetHashCode 在您關心對象值的情況中非常有用。 具有相同內容的兩個字符串將為 Object.GetHashCode 返回相同的值。 2. RuntimeHelpers.GetHashCode 在您關心對象標識的情況中非常有用。 具有相同內容的兩個字符串將為 RuntimeHelpers.GetHashCode 返回不同值,因為盡管其內容相同,但這兩個字符串屬不同的字符串對象。
現在我們應該理解為什么Object.GetHashCode方法根據當前對象實例的地址來計算的了,因為他調用的是RuntimeHelpers.GetHashCode,其實微軟本意是讓Object.GetHashCode方法通過對象實例的字段值來計算的,這也就是我們需要重寫Object.GetHashCode方法的原因。
當前GetHashCode方法的現狀
在當前的.NET中只有兩種數據類型,一種是引用類型一種是值類型,引用類型直接繼承Object基類,所以也沒什么好說的,自己新建的類重寫GetHashCode即可,值類型是繼承ValueType基類的,ValueType已經重寫了GetHashCode方法,但是里面用到了反射,這種方式對性能是有影響的,所以即使是值類型,也建議重寫GetHashCode方法。最后,列出Jeffrey Richter對重寫GetHashCode方法的意見:
1. 算法要提供良好的隨機分布,是哈希表獲得最佳性能。
2. Object或者ValueType的GetHashCode方法不屬於好性能哈希算法,盡量不要調用。
3. 算法應該至少使用一個實例字段
4. 理想情況下,算法使用的字段應該是不可變的,也就是說,字段應該在對象構造時初始化,在對象生存期內永不改變
5. 算法應該盡可能快的執行
6. 包含相同值的不同對象應返回相同的哈希碼。