改善C#程序的157个建议(连载12)

发表于:2011-11-01 09:39

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:陆敏技    来源:51Testing软件测试网采编

  建议12:重写Equals时也要重写GetHashCode

  除非考虑到自定义类型会被用作基于散列的集合的键值;否则,不建议重写Equals方法,因为这会带来一系列的问题。

  如果编译上一个建议中的Person这个类型,编译器会提示这样一个信息:

  “重写 Object.Equals(object o)但不重写 Object.GetHashCode()”

  如果重写Equals方法的时候不重写GetHashCode方法,在使用如FCL中的Dictionary类时,可能隐含一些潜在的Bug。还是针对上一个建议中的Person进行编码,代码如下所示:

  1. static Dictionary<Person, PersonMoreInfo> PersonValues = new Dictionary<Person,  
  2.     PersonMoreInfo>();  
  3. static void Main(string[] args)  
  4. {  
  5.     AddAPerson();  
  6.     Person mike = new Person("NB123");  
  7.     //Console.WriteLine(mike.GetHashCode());  
  8.     Console.WriteLine(PersonValues.ContainsKey(mike));  
  9. }  
  10. static void AddAPerson()  
  11. {  
  12.     Person mike = new Person("NB123");  
  13.     PersonMoreInfo mikeValue = new PersonMoreInfo() { SomeInfo = "Mike's info" };  
  14.     PersonValues.Add(mike, mikeValue);  
  15.     //Console.WriteLine(mike.GetHashCode());  
  16.     Console.WriteLine(PersonValues.ContainsKey(mike));  
  17. }

  本段代码的输出将会是:

  1. True  
  2. False

  理论上来说,在上一个建议中我们已经重写了Person的Equals方法;也就是说,在AddAPerson方法中的mike和Main方法中的mike属于“值相等”。于是,将该“值”作为key放入Dictionary中,再在某处根据mike将mikeValue取出来,这会是理所当然的事情。可是,从上面的代码段中我们发现,针对同一个示例,这种结论是正确的,若是针对不同的mike示例,这种结果就有问题了。

  基于键值的集合(如上面的Dictionary)会根据Key值来查找Value值。CLR内部会优化这种查找,实际上,最终是根据Key值的HashCode来查找Value值。代码运行的时候,CLR首先会调用Person类型的GetHashCode,由于发现Person没有实现GetHashCode,所以CLR最终会调用Object的GetHashCode方法。将上面代码中的两行注释代码去掉,运行程序得到输出,我们会发现,Main方法和AddAPerson方法中的两个mike的HashCode是不同的。这里需要解释为什么两者实际对应调用的Object.GetHashCode会不相同。

  Object为所有的CLR类型都提供了GetHashCode的默认实现。每new一个对象,CLR都会为该对象生成一个固定的整型值,该整型值在对象的生存周期内不会改变,而该对象默认的GetHashCode实现就是对该整型值求HashCode。所以,在上面代码中,两个mike对象虽然属性值都一致,但是它们默认实现的HashCode不一致,这就导致Dictionary中出现异常的行为。若要修正该问题,就必须重写GetHashCode方法。Person类的一个简单的重写可以是如下的形式:

  1. public override int GetHashCode()  
  2. {  
  3.     return this.IDCode.GetHashCode();  
  4. }

  此时再运行本条建议开始时代码的输出,就会发现两者的HashCode是一致的,而Dictionary也会找到相应的键值,输出:True。

  细心的读者可能已经发现一个问题,Person类的IDCode属性是一个只读属性。从语法特性本身来讲,可以将IDCode设置为可写;然而从现实的角度考虑,一个“人”一旦踏入社会,其IDCode就不应该改变,如果要改变,就相当于是另外一个人了。所以,我们应该只实现该IDCode的只读属性。同理,GetHashCode方法也应该基于那些只读的属性或特性生成HashCode。

21/212>
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号