Effective C# Item10:理解GetHashCode()方法的缺陷

    GetHashCode()方法的用途:为一个基于散列的集合定义键的散列值,典型的散列集合包括HashTable和Dictionary。如果我们定义的类型在散列集中不会被用作键的话,那么不用关心它的GetHashCode()方法是否高效和正确。

    作为类型的散列值,它应该满足以下三条规则:

  1. 如果两个对象相等,那么它们必须产生相同的散列值,否则,这样的三列码不能用来查找集合中的对象。
  2. 对于任何一个对象A,A.GetHashCode()方法必须是一个实例不变式(invariant),即不管在A上调用什么方法,A.GetHashCode()都必须总是返回相同的值,,者可以确保放在“散列桶”中的对象总是位于正确的“散列桶”中。
  3. 对于所有的输入,散列函数应该在所有的整数中产生一个随机的分布,这样,我们才能从一个散列容器上获得效率的提升。

    通过上述三条规则,我们可以理解:为什么我们在重写了类型实例的Equals方法后,要重写GetHashCode()方法,因为如果不重写,会违反上述规则1。

    引用类型和值类型的基类,都对GetHashCode()方法提供了默认的实现。System.Object类的实现方式:使用一个内部字段来产生散列值,系统创建的每一个对象在创建时都会有一个唯一的对象键,这些键从1开始,每创建一个新的对象,键值就会增1,对象标识字段在对象创建后,是不可以再更改的,而GetHashCode()方法,就会将这个对象键值返回;而System.ValueType提供的默认方式:返回类型中定义的第一个字段的散列码。

   我们来分析上述默认实现是否满足了散列值的三条规则,首先,对于System.Object,在不重写Equals的情况下,是满足规则1的;规则2是可以满足的;规则3则只有在大量对象的情况下,才会满足,否则,散列值会集中在整数的低端。

   然后,我们再看ValueType,在不重写Equals,或者重写Equals并且将第一个字段作为比较条件时,是满足规则1的;如果类型中的第一个字段是只读的,那么是可以满足规则2的;至于是否可以满足规则3,就要看类型中第一个字段是否在整数中有一个随机分布了。

 

    我们可以自己重写GetHashCode()方法,但是一定要满足上述3条规则,其中规则1和规则2可以保证生成的散列码是正确的;规则3可以保证生成的散列码是高效的。

posted @ 2010-01-10 22:32  李潘  阅读(845)  评论(0编辑  收藏  举报