为什么覆盖了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 @ 2009-08-05 00:07  Kevin Dai  阅读(774)  评论(0编辑  收藏  举报