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