GetHashCode()方法解析


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不同

image

然后我們在第一次打印出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是相同的

image

測試結果

        從上面的例子我們可以推測,雖然我們無法獲取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. 包含相同值的不同對象應返回相同的哈希碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM