1588

给定以下课程

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它代表了Foos 表的一行。哪个是覆盖的首选方法GetHashCode

为什么覆盖很重要GetHashCode

4

15 回答 15

1420

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

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

  • 如果两件事相等(Equals(...) == true),那么它们必须返回相同的值GetHashCode()
  • 如果GetHashCode()相等,则它们不必相同;这是一个冲突,Equals将被调用以查看它是否是一个真正的平等。

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

在现代框架中,该HashCode类型具有帮助您从多个值创建哈希码的方法;在较旧的框架上,您需要不使用,例如:

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

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


当你弄错时会发生什么的演示在这里

于 2008-12-16T13:47:48.673 回答
151

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

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

于 2008-12-21T12:39:21.113 回答
70

通过覆盖 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;
    }
}

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

于 2008-12-16T13:48:38.377 回答
43

请不要忘记null在覆盖时检查 obj 参数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

于 2011-11-17T07:46:56.567 回答
41

怎么样:

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

假设性能不是问题:)

于 2010-11-25T00:48:20.767 回答
17

作为.NET 4.7覆盖的首选方法GetHashCode()如下所示。如果针对较旧的 .NET 版本,请包含System.ValueTuple nuget包。

// C# 7.0+
public override int GetHashCode() => (FooId, FooName).GetHashCode();

在性能方面,这种方法将优于大多数复合哈希码实现。ValueTuple是一个,所以不会有任何垃圾,并且底层算法是尽可能快的struct

于 2020-05-11T12:52:37.133 回答
16

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

  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");
    }
}
于 2013-11-19T10:17:21.473 回答
16

只是添加上面的答案:

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

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

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

于 2014-11-12T13:48:02.457 回答
14

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

于 2008-12-16T13:48:32.120 回答
10

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

于 2012-02-21T11:36:55.887 回答
8

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

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

于 2011-06-26T23:21:09.240 回答
4

您应该始终保证如果两个对象相等,如 Equals() 定义的那样,它们应该返回相同的哈希码。正如其他一些评论所述,理论上,如果对象永远不会在基于哈希的容器(如 HashSet 或 Dictionary)中使用,则这不是强制性的。不过,我建议您始终遵循此规则。原因很简单,因为人们太容易将集合从一种类型更改为另一种类型,其目的是实际提高性能或只是以更好的方式传达代码语义。

例如,假设我们将一些对象保存在 List 中。一段时间后,有人真正意识到 HashSet 是一个更好的选择,因为它具有更好的搜索特性。这是我们可能遇到麻烦的时候。List 将在内部使用类型的默认相等比较器,这意味着在您的情况下为 Equals,而 HashSet 使用 GetHashCode()。如果两者的行为不同,您的程序也会如此。请记住,此类问题并不是最容易解决的问题。

我在一篇文中总结了这种行为以及其他一些 GetHashCode() 陷阱,您可以在其中找到更多示例和解释。

于 2020-02-12T20:00:32.447 回答
3

C# 9(.net 5 或 .net core 3.1)开始,您可能希望像使用Value Based Equality一样使用记录

于 2021-01-15T11:18:32.683 回答
0

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

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

于 2013-10-07T17:06:53.963 回答
-7

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

    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;  
    }
于 2014-03-14T18:08:28.467 回答