覆盖Equals和GetHashCode方法

5

我看到过这样的说法,如果你在一个类/对象上重写了Equals方法,那么你需要同时重写GetHashCode方法。

 public class Person : IEquatable<Person>
    {
        public int PersonId { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }

        public Person(int personId, string firstName, string lastName)
        {
            PersonId = personId;
            FirstName = firstName;
            LastName = lastName;

        }

        public bool Equals(Person obj)
        {
            Person p = obj as Person;

            if (ReferenceEquals(null, p)) 
                return false;
            if (ReferenceEquals(this, p)) 
                return true;

            return Equals(p.FirstName, FirstName) && 
                   Equals(p.LastName, LastName);
        }


    }

现在假设有以下情况:
 public static Dictionary<Person, Person> ObjDic= new Dictionary<Person, Person>();
 public static Dictionary<int, Person> PKDic = new Dictionary<int, Person>();

不覆盖 GetHashCode 方法会影响上述两个字典吗?我的基本问题是,如何生成 GetHashCode?如果我仍然在 PKDic 中查找对象,我是否只能基于 PK 找到它。如果我想要覆盖 GetHashCode,该怎么做?


请查看此链接在SO上的内容:http://stackoverflow.com/search?q=gethashcode - Srinivas Reddy Thatiparthy
为什么你不比较“PersonId”? - SLaks
4个回答

4
您应该始终重写 GetHashCode
一个 Dictionary<int, Person> 可以在没有 GetHashCode 的情况下运行,但是一旦调用像 DistinctGroupBy 这样的 LINQ 方法,它将停止工作。
顺便提一下,您实际上还没有重写 EqualsIEquatable.Equals 方法与从 Object 继承的 virtual bool Equals(object obj) 不同。尽管默认的 IEqualityComparer<T> 如果类实现了它,则会使用 IEquatable<T> 接口,但您仍然应该重写 Equals,因为其他代码可能不会这样做。
在您的情况下,您应该像这样重写 EqualsGetHashCode:
public override bool Equals(object obj) { return Equals(obj as Person); }
public override int GetHashCode() {
    return FirstName.GetHashCode() ^ LastName.GetHashCode();
}

-1 真的没有回答 OP 正在问的任何问题。 - Rob Levine
移除了-1,特别是针对您没有实际重写.Equals的情况! - Rob Levine

4
在您的场景中,如果不覆盖您的类型上的GetHashCode,它只会影响第一个字典,因为密钥是用于哈希的,而不是值。
在查找键的存在时,Dictionary<TKey, TValue>将使用哈希码来查找是否有任何可能相等的键。请注意,哈希是一个可以确定两个事物是否可能相等或非常可能相等的值。严格地说,哈希不能确定两个项目是否相等。
两个相等的对象需要返回相同的哈希码。但是,两个非相等的对象不需要返回不同的哈希码。换句话说,如果哈希码不匹配,则可以保证对象不相等。如果哈希码匹配,则对象可能是相等的。
因此,如果哈希码不匹配,则Dictionary只调用Equals了比较两个对象。
至于“如何重写GetHashCode”,这是一个复杂的问题。通常,散列算法应在值集合中提供代码的均匀分布和低碰撞率之间的平衡(碰撞是两个非相等的对象产生相同代码的情况)。这是一件容易描述但很难实现的事情。很容易做到其中一个,但很难平衡两者。
从实际角度来看(即忽略性能),您可以将名字的第一个和最后一个字符全部XOR(或甚至使用它们各自的哈希码,如Joel建议)作为哈希码。这将产生较低的碰撞率,但不会导致非常均匀的分布。除非您处理非常大的集合或非常频繁的查找,否则这不会成为问题。

3
您的GetHashCode()和Equals()方法应该如下所示:
public int GetHashCode()
{
    return (FirstName.GetHashCode()+1) ^ (LastName.GetHashCode()+2);
}


public bool Equals(Object obj)
{
    Person p = obj as Person;

    if (p == null) 
        return false;

    return this.Firstname == p.FirstName && this.LastName == p.Lastname;
}

规则是GetHashCode()必须使用在确定.Equals()方法相等性中使用的字段。
至于您问题中字典部分,.GetHashCode()用于确定字典中的键。但是,对于您问题中的每个字典,这具有不同的影响。
具有int键(可能是您的人员ID)的字典将使用整数的GetHashCode(),而另一个字典(ObjDic)将使用来自您的Person对象的GetHashCode()。因此,PKDic将始终区分具有不同ID的两个人,而ObjDic可能将具有不同ID但相同名字的两个人视为同一记录。

4
你的.Equals实现不正确。两个实例具有相同的哈希码并不意味着它们是相同的。将两个字符串进行异或运算后得到的哈希码可以与另外两个不同的字符串进行异或运算后得到的哈希码相同。你应该逐个检查Firstname和Lastname字段。 - Rob Levine
2
错误。哈希码可以相等,即使相应的字符串不同。 - Thomas
修复了.Equals()方法。我知道哈希可能会发生冲突,但我很确定我曾经在某个地方读到过,.Equals()的结果应该与GetHashCode()的结果匹配,无论如何都是如此。在没有权威参考的情况下,我暂时会屈服于同行的压力。 - Joel Coehoorn
问题在于,“Brandt Gideon”(我的儿子)的哈希码与“Gideon Brandt”(另一个真实存在的人,谷歌上有记录)相同。这不仅会破坏“Equals”方法,还会导致“GetHashcode”输出的分布不够优化。我应该指出的是,通常情况下我可以使用XOR作为哈希码生成器,但在这种情况下它可能会在“Dictionary”集合中引起哈希冲突。 - Brian Gideon
@Brian - 啊,你说得对。我忘了将位置元素添加到构成结果的各个哈希中。 - Joel Coehoorn
@Joel:是的,因为XOR是可结合的。我刚试了一下,并确认FirstName ^ LastName = 68954133适用于两个测试案例。 - Brian Gideon

1

这是我会做的方式。由于两个不同的人拥有完全相同的姓名很常见,因此使用唯一标识符(您已经拥有)更有意义。

public class Person : IEquatable<Person>
{
  public override int GetHashCode()
  {
    return PersonId.GetHashCode();
  }

  public override bool Equals(object obj)
  {
    var that = obj as Person;
    if (that != null)
    {
      return Equals(that);
    }
    return false;
  }

  public bool Equals(Person that)
  {
    return this.PersonId == that.PersonId;
  }
}

回答您的具体问题:这只有在您将Person作为集合中的键使用时才会产生影响。例如:Dictionary<Person, string>SortedDictionary<Person, Foo>,但不包括 Dictionary<int, Person>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接