dinghao

记录成长点滴

 

GetHashCode、Equals、IComparable的重载方式

第一个 GetHashCode
    这个方法争议最多,主要原因是
MSDN的每个版本对他的说明都不同。

 

只要看一下MSDN,就会发现它对这个方法的说明至少有三个版本:

 

最早的Derived classes must override GetHashCode with an implementation that returns a unique hash code

 

.net1.12.0

1.如果两个类型相同的对象表示相同的值,则哈希函数必须为两个对象返回相同的常数值。

 

2.为了获得最佳性能,哈希函数必须为所有输入生成随机分布。

 

3.不论对该对象进行什么样的更改,哈希函数都必须返回完全相同的值

 

.net3.0

 

1. If two objects compare as equal, the GetHashCode method for each object must return the same value. However, if two objects do not compare as equal, the GetHashCode methods for the two object do not have to return different values.

 

2.The GetHashCode method for an object must consistently return the same hash code as long as there is no modification to the object state that determines the return value of the object's Equals method. Note that this is true only for the current execution of an application, and that a different hash code can be returned if the application is run again.

 

3.For the best performance, a hash function must generate a random distribution for all input.

 

最早版本我没有见到过,不过很容易发现它的错误,一个32位的值不可能不重复。Ms没有办法保证HahsCode是唯一值,他保证的只是在对象的生命期中的唯一,因此有下面的描述:HashCode绝对不能当作唯一值。

 

The default implementation of GetHashCode does not guarantee uniqueness or consistency; therefore, it must not be used as a unique object identifier for hashing purposes

 

有人计算过,普通电脑每秒CLR可以创建、回收2000万个object,这也表明唯一是不可能的,它的充分几率也是很高的。

 

第二个版本:这种描述会带来一个很怪的问题:可变类型只有唯一一种实现方式,就是根据一个不变值产生HashCode,这样就必然违反2

 

13有时候也是矛盾的,如下面的例子,

 

1、创建一个类的两个实例,实例值不同。这样根据1要产生两个不同的HashCode

 

2、改变其中一个的值,使他们相同。这样根据1,要有相同的HashCode

 

3、再看看3说什么?对实例的改变不能影响HashCode!!矛盾的。

 

如果要满足三个条件对不可变类型是不可能的,唯一可行的就是根据不可变值产生HashCode,如:模拟CLR的实现,用对象的标志产生HashCode,如有个静态变量,每产生一个实例就加一;用readonly,总之是根据不可变值产生HashCode

 

条件一还隐含了:如果值被用来计算hashCode,也必须用来计算Equals,否则会有很奇怪的行为。那就是GetHashCode不起作用,这是因为GetHashCode会调用Equals,只有Equals等于False才会认为是唯一key

 

第三种描述是正确的指明了GetHashCodeEquals是相关的。其实这种关系在.net2.01.1的代码没有看,应该也如此)就存在,.net3.0关于GetHashCode的说明也就是2.0的说明,虽然Msdn把他放到了3.0的文档中,这特点并不是.net3.0特有的,修改的只是文档。

 

GetHashCode的本质是参考.net2.0的反射代码):Hashtable中增加keyValue会调用KeyGetHashCodeGetHashCode又会循环调用重载的Equals如果Equalsfalse就会认为是唯一key就可以使用如果任何一个为ture则不可以用作key,即使HashCode有唯一值。即key并不是GetHashCode唯一决定的,还要看Equals,只要弄明白了这一点在重写GetHashCode时就不会有错了。

 

如果看看.net自己对此方法的重载就会发现,可变类型是没有重载此方法的。这是不是我们可以参考的呢?不可变类型不会有上面的这些问题,当然可以放心的用了。

 

第二个 Equals

 

Equals相对简单,只要根据几条规范写就可以了。但是有些.net类在内部会调用Equals,因此也要小心。

 

关于GetHashCodeEquals的重载

 

我听到的说法是从最初的两个必须同时重载到重载GetHashCode不必重载Equals再到都必须重载。从这些天查的资料看,这两者是必须同时重载的。因为在Add时,hashTable会调用GetHashCodeGetHashCode会调用KeyEquals

 

第三个IComparable

 

曾经在某个资料上看到过这样的描述:必须只有不可变类才可以重载他。原因我一直不清楚,或许和重载IComparable必须重载Equals相关。但是看看.net类库,重载他的全是不可变类。另外重载此方法还应该重载几个比较运算符,否则会对调用者产生困扰。

 

总之,

 

这几个方法都不会抛出异常,不会有编译错误,运行时错误也不一定会发生,调试很难,这也就让许多错误的实现在一直正确运行,但是他们总是一颗地雷,也许某个时间就会爆炸。

 

这三个方法是绑定到一起的,重载任何一个,就必须重载另外两个。如果是可变类型就要尽可能的不去重载这三个方法,在.net类库中我没有找到可变类型重载过他们,这很可能是Ms的推荐方式。因为重载他们很可能引入新问题,用到相等比较时最好还是自己写一个新的Equals方法。

 

 

 

 

posted on 2007-04-13 20:43  思无邪  阅读(3416)  评论(7编辑  收藏  举报

导航