SauronKing

写随笔只是为了记录自己的曾经,如果能给您带来些许方便,那是我莫大的荣幸!

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

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. 包含相同值的不同对象应返回相同的哈希码。

posted on 2016-10-10 17:21  SauronKing  阅读(3618)  评论(0编辑  收藏  举报