给定以下课程

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

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

        if (fooItem == null) 
        {
           return false;
        }

        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;
    }
}

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

请不要忘记检查 obj 参数 null 当覆盖时 Equals()。还要比较类型。

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

    if (fooItem == null)
    {
       return false;
    }

    return fooItem.FooId == this.FooId;
}

原因是: Equals 比较时必须返回 false null. 。也可以看看 http://msdn.microsoft.com/en-us/library/bsc2ak47.aspx

怎么样:

public override int GetHashCode()
{
    return string.Format("{0}_{1}_{2}", prop1, prop2, prop3).GetHashCode();
}

假设性能不是问题:)

我们有两个问题需要解决。

  1. 你无法提供一个合理的 GetHashCode() 如果可以更改对象中的任何字段。同样,对象通常永远不会在取决于的集合中使用 GetHashCode(). 。因此实施成本 GetHashCode() 通常不值得,或者是不可能的。

  2. 如果有人将您的对象放入调用的集合中GetHashCode() 并且你已经覆盖了 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;

        if (fooItem == null)
        {
           return false;
        }

        return fooItem.FooId == this.FooId;
    }

    public override int GetHashCode()
    {
        // Some comment to explain if there is a real problem with providing GetHashCode() 
        // or if I just don't see a need for it for the given class
        throw new Exception("Sorry I don't know what GetHashCode should do for this class");
    }
}

因为框架要求两个相同的对象必须有相同的hashcode。如果重写 equals 方法对两个对象进行特殊比较,并且该方法认为两个对象相同,那么两个对象的哈希码也必须相同。(字典和哈希表依赖于这个原则)。

只是补充上面的答案:

如果您不重写 Equals,则默认行为是比较对象的引用。这同样适用于哈希码 - 默认实现通常基于引用的内存地址。因为您确实覆盖了 Equals,这意味着正确的行为是比较您在 Equals 上实现的内容而不是引用,因此您应该对哈希码执行相同的操作。

您的类的客户端将期望 hashcode 具有与 equals 方法类似的逻辑,例如使用 IEqualityComparer 的 linq 方法首先比较 hashcode,只有当它们相等时,才会比较 Equals() 方法,这可能会更昂贵运行时,如果我们没有实现 hashcode,相等的对象可能会有不同的 hashcode(因为它们有不同的内存地址),并且会被错误地确定为不相等(Equals() 甚至不会命中)。

此外,除了在字典中使用对象时可能无法找到对象的问题(因为它是由一个哈希码插入的,当您查找它时,默认哈希码可能会有所不同,并且 Equals() 甚至不会被调用,就像 Marc Gravell 在他的回答中解释的那样, 您还引入了对字典或哈希集概念的违反,该概念不应允许相同的键 - 当您覆盖 Equals 时,您已经声明了这些对象本质上是相同的,因此您不希望它们都作为假定具有唯一键的数据结构上的不同键。但由于它们具有不同的哈希码,因此“相同”的密钥将作为不同的密钥插入。

哈希码用于基于哈希的集合,如 Dictionary、Hashtable、HashSet 等。此代码的目的是通过将特定对象放入特定组(桶)来非常快速地对其进行预排序。当您需要从哈希集合检索该对象时,这种预排序非常有助于查找该对象,因为代码必须仅在一个存储桶中搜索您的对象,而不是在它包含的所有对象中搜索。哈希码的分布越好(唯一性越好),检索速度越快。在理想情况下,每个对象都有唯一的哈希码,找到它是一个 O(1) 操作。在大多数情况下,它接近 O(1)。

这并不一定重要;这取决于您的馆藏大小和性能要求,以及您的类是否将在您可能不知道性能要求的库中使用。我经常知道我的集合大小不是很大,而且我的时间比通过创建完美哈希码获得的几微秒的性能更有价值;所以(为了摆脱编译器烦人的警告)我只需使用:

   public override int GetHashCode()
   {
      return base.GetHashCode();
   }

(当然我也可以使用#pragma 来关闭警告,但我更喜欢这种方式。)

当你处于你的位置时 当然,需要比其他人在这里提到的所有问题都适用的性能。 最重要的 - 否则,当从哈希集或字典中检索项目时,您将得到错误的结果: 哈希码不得随对象的生命周期而变化 (更准确地说,在需要哈希码时,例如作为字典中的键时):例如,以下内容是错误的,因为 Value 是公共的,因此可以在实例的生命周期内从外部更改到类,因此您不得将其用作哈希码的基础:


   class A
   {
      public int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //WRONG! Value is not constant during the instance's life time
      }
   }    

另一方面,如果 Value 无法更改,则可以使用:


   class A
   {
      public readonly int Value;

      public override int GetHashCode()
      {
         return Value.GetHashCode(); //OK  Value is read-only and can't be changed during the instance's life time
      }
   }

据我了解,原始的 GetHashCode() 返回对象的内存地址,因此如果您想比较两个不同的对象,则必须重写它。

编辑:这是不正确的,原来的 GetHashCode() 方法不能保证 2 个值相等。尽管相等的对象返回相同的哈希码。

考虑到公共属性,下面使用反射在我看来是一个更好的选择,因为这样你就不必担心添加/删除属性(尽管不是很常见的情况)。我发现这也表现得更好。(使用 Diagonistics 秒表比较时间)。

    public int getHashCode()
    {
        PropertyInfo[] theProperties = this.GetType().GetProperties();
        int hash = 31;
        foreach (PropertyInfo info in theProperties)
        {
            if (info != null)
            {
                var value = info.GetValue(this,null);
                if(value != null)
                unchecked
                {
                    hash = 29 * hash ^ value.GetHashCode();
                }
            }
        }
        return hash;  
    }
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top