为什么覆盖了Equals方法后还要覆盖GetHashCode方法呢?

最近在读《Effective Java》,认为书中的每一条都十分经典。这回就简单记录一下Item 9。Item 9讲述的是“覆盖equals时总要覆盖hashCode”。我猜测,这条建议同时也会对C#适用。

请看下面这段代码:

   1:  using System;
   2:  using System.Collections.Generic;
   3:  using System.Linq;
   4:  using System.Text;
   5:  using System.Collections;
   6:  namespace EqualsGetHashCode
   7:  {
   8:      class Program
   9:      {
  10:          static void Main(string[] args)
  11:          {
  12:              Dictionary<PhoneNumber, string> m = new Dictionary<PhoneNumber, string>();
  13:              m.Add(new PhoneNumber(707,867,5309), "Jenny");
  14:              Console.WriteLine(m[new PhoneNumber(707, 867, 5309)]==null);
  15:              Console.ReadKey();
  16:          }
  17:      }
  18:      public class PhoneNumber
  19:      {
  20:          public short AreaCode
  21:          {
  22:              get;
  23:              set;
  24:          }
  25:          public short Prefix
  26:          {
  27:              get;
  28:              set;
  29:          }
  30:          public short LineNumber
  31:          {
  32:              get;
  33:              set;
  34:          }
  35:          public PhoneNumber(int areaCode,int prefix,int lineNumber)
  36:          {
  37:              RangeCheck(areaCode, 999, "area code");
  38:              RangeCheck(prefix, 999, "prefix");
  39:              RangeCheck(lineNumber, 9999, "line number");
  40:              this.AreaCode = (short)areaCode;
  41:              this.Prefix = (short)prefix;
  42:              this.LineNumber = (short)lineNumber;
  43:          }
  44:          public static void RangeCheck(int arg, int max, string name)
  45:          {
  46:              if (arg < 0 || arg > max)
  47:              {
  48:                  throw new ArgumentOutOfRangeException("arg", "arg must be less or equal than max");
  49:              }
  50:          }
  51:          public override bool Equals(object obj)
  52:          {
  53:              if (obj == this)
  54:              {
  55:                  return true;
  56:              }
  57:              if (typeof(PhoneNumber) != obj.GetType())
  58:              {
  59:                  return false;
  60:              }
  61:              PhoneNumber phoneNumber = obj as PhoneNumber;
  62:              return phoneNumber.LineNumber == LineNumber
  63:                  && phoneNumber.AreaCode == AreaCode
  64:                  && phoneNumber.Prefix == Prefix;
  65:          }
  66:      }
  67:  }

运行以上代码会抛出异常KeyNotFoundException。从字面来看,添加的PhoneNumber实例和欲检索的PhoneNumber实例是“等同”的,因为各个成员值都是相等的。但是运行时,却抛出异常。当我在用Java实验时,返回的会是null。也就是说未能在容器中找到与检索的实例,根据《Effective Java》便知是hashCode的问题了。既然实例相等,那么它们的哈希值也应该相等(来自Java的Object规范)。在Visual Studio中,发现当你进覆盖Equals方法而未实现GetHashCode方法时,会提示“EqualsGetHashCode.PhoneNumber' overrides Object.Equals(object o) but does not override Object.GetHashCode() ”。从中可以推出,在字典中检索时,是根据哈希值进行比较的。所以GetHashCode没有被覆盖,即使欲检索的实例在字典中存在,但是由于哈希函数没有被重写,最后结果还是不能如您预期。所以我们还要实现GetHashCode方法,而且还要求在语义上要和Equals方法一致。

     添加一个GetHashCode方法:

   1:  public override int GetHashCode()
   2:   {
   3:              int result = 17;
   4:              result = 31 * result + AreaCode;
   5:              result = 31 * result + Prefix;
   6:              result = 31 * result + LineNumber;
   7:              return result;
   8:   }

现在再运行,发现控制台输出:False。

总结

当你覆盖Equals方法时,一定要覆盖GetHashCode方法,否则当你在使用一些容器类型时,会出现与您预期相违的结果。因为容器类是根据GetHashCode方法来和容易里的键做哈希值比较,所以要覆盖GetHashCode方法。其实这也符合常规,既然两个实例相等了,那它们的哈希值不相等也无道理的吧。所以请记住:当你覆盖Equals方法时,一定要覆盖GetHashCode方法。

posted @   Kevin Dai  阅读(775)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
点击右上角即可分享
微信分享提示