协慌网

登录 贡献 社区

为什么在重写 Equals 方法时重写 GetHashCode 很重要?

鉴于以下课程

public class Foo
{
    public int FooId { get; set; }
    public string FooName { get; set; }

    public override bool Equals(object obj)
    {
        Foo fooItem = obj as Foo;

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Which is preferred?

        return base.GetHashCode();

        //return this.FooId.GetHashCode();
    }
}

我已经覆盖了Equals方法,因为Foo代表的一排Foo桌上。哪个是覆盖GetHashCode的首选方法?

为什么重写GetHashCode很重要?

答案

是的,重要的是如果您的项目将用作字典中的键或HashSet<T>等 - 因为这是使用(在没有自定义IEqualityComparer<T> )将项目分组到存储桶中。如果两个项的哈希码不匹配,它们可能永远不会被认为是相等的( Equals将永远不会被调用)。

GetHashCode()方法应该反映Equals逻辑; 规则是:

  • 如果两个东西相等( Equals(...) == true )那么它们必须GetHashCode()返回相同的值
  • 如果GetHashCode()是相等的, 没有必要对他们是相同的; 这是一个碰撞,并且将调用Equals以查看它是否是真正的平等。

在这种情况下,它看起来像 “ return FooId; ” 是一个合适的GetHashCode()实现。如果您正在测试多个属性,通常使用下面的代码组合它们,以减少对角线冲突(即,使new Foo(3,5)具有与new Foo(5,3)不同的哈希码):

unchecked // only needed if you're compiling with arithmetic checks enabled
{ // (the default compiler behaviour is *disabled*, so most folks won't need this)
    int hash = 13;
    hash = (hash * 7) + field1.GetHashCode();
    hash = (hash * 7) + field2.GetHashCode();
    ...
    return hash;
}

哦 - 为方便起见,您还可以考虑在重写EqualsGetHashCode时提供==!=运算符。


当你弄错了会发生什么事的证明就在这里

实际上很难正确实现GetHashCode() ,因为除了 Marc 已经提到的规则之外,哈希代码在对象的生命周期内不应该改变。因此,用于计算哈希码的字段必须是不可变的。

当我使用 NHibernate 时,我终于找到了解决这个问题的方法。我的方法是从对象的 ID 计算哈希码。只能通过构造函数设置 ID,因此如果要更改 ID,这是非常不可能的,您必须创建一个具有新 ID 的新对象,因此需要新的哈希代码。这种方法最适用于 GUID,因为您可以提供随机生成 ID 的无参数构造函数。

通过重写 Equals,您基本上声明自己是更了解如何比较给定类型的两个实例的人,因此您很可能是提供最佳哈希码的最佳候选者。

这是 ReSharper 如何为您编写 GetHashCode()函数的示例:

public override int GetHashCode()
{
    unchecked
    {
        var result = 0;
        result = (result * 397) ^ m_someVar1;
        result = (result * 397) ^ m_someVar2;
        result = (result * 397) ^ m_someVar3;
        result = (result * 397) ^ m_someVar4;
        return result;
    }
}

正如您所看到的,它只是尝试根据类中的所有字段猜测一个好的哈希代码,但由于您知道对象的域或值范围,您仍然可以提供更好的哈希代码。