9
11
2015
0

[Effective C#] Item 7: Understand the Pitfalls of GetHashCode()


这是《Effective C#》中讲的唯一一个要避免写的方法。。。

GetHashCode()方法用于获取一个对象的哈希值,它通常是在基于哈希的容器中被用作键值,例如HashSet<T>和Dictionary<K, V>。

然而,问题在于,默认的GetHashCode()并不是很好用,更糟糕的是,有时甚至写不出一个较好的实现。

哈希值应遵守以下三个原则:

1. 如果两个对象相等,它们的哈希值必须相等。否则,哈希值就不能用于在哈希容器中查找值了。

2. 一个对象的哈希值必须在它的生存周期中不变。它确保对象在哈希容器中总是被放在正确的位置。

3. 哈希值必须在int范围随机地分布。它使得哈希容器有较高的效率。

现在用着三个标准来评判默认的哈希值。

所有引用类型在创建时会拥有一个唯一的对象键值。这个值对于应用程序的生存周期中的第一个对象是1,然后每创建一个对象,这个值就会递增。显然,它符合前两个原则,除非你重定义了相等性。但是,这个哈希值都集中在int的一个较小范围内,所以如果在哈希容器中使用默认的哈希值,性能会受到很大的影响。

ValueType重写了GetHashCode()方法。值类型依据对象的第一个字段来生成哈希值。和引用类型一样,除非你重定义了相等性,第一个原则是被遵守的。但是对于第二个原则,糟糕的事情发生了,如果修改了第一个字段,哈希值就会改变,这违反了第二个原则,所以,值类型的哈希值有可能是错误的。一个较好的解决方案是使对象“不可变”,这将在Item 20中被讨论。第三个原则则不一定,它取决于值类型的第一个字段。

下面看一个例子。

如果字段epoch被设置为当前日期(不包括时间),那么MyStruct对象的哈希值就很用可能相同,这会在使用哈希容器时造成性能损失。

下面是另一个例子。

在修改了c1的Name后,它的哈希值就改变了,myDic中加入的那个对象丢失了,因为myDic中记录的哈希值是加入时生成的。

下面是修改了以后的Customer类。

现在name字段是不可变的,修改它时实际上生成了一个新的对象。现在就不会造成上面的问题了。虽然的代码要长一些,但总比出错好。

对于第三条原则,还可以多做一些讨论。为了生成随机分布的数字,一个不错的算法是取所有字段的哈希值异或起来的值。当然,计算时应该排除可变的字段以符合第二条原则。

Category: C#及OOP | Tags: Effective C# | Read Count: 467

登录 *


loading captcha image...
(输入验证码)
or Ctrl+Enter

Host by is-Programmer.com | Power by Chito 1.3.3 beta | Theme: Aeros 2.0 by TheBuckmaker.com