【问题标题】:What's the role of GetHashCode in the IEqualityComparer<T> in .NET?GetHashCode 在 .NET 中的 IEqualityComparer<T> 中的作用是什么?
【发布时间】:2010-11-04 09:43:57
【问题描述】:

我正在尝试了解接口IEqualityComparer的GetHashCode方法的作用。

以下示例取自 MSDN:

using System;
using System.Collections.Generic;
class Example {
    static void Main() {
        try {

            BoxEqualityComparer boxEqC = new BoxEqualityComparer();

            Dictionary<Box, String> boxes = new Dictionary<Box,
                                                string>(boxEqC);

            Box redBox = new Box(4, 3, 4);
            Box blueBox = new Box(4, 3, 4);

            boxes.Add(redBox, "red");
            boxes.Add(blueBox, "blue");

            Console.WriteLine(redBox.GetHashCode());
            Console.WriteLine(blueBox.GetHashCode());
        }
        catch (ArgumentException argEx) {

            Console.WriteLine(argEx.Message);
        }
    }
}

public class Box {
    public Box(int h, int l, int w) {
        this.Height = h;
        this.Length = l;
        this.Width = w;
    }
    public int Height { get; set; }
    public int Length { get; set; }
    public int Width { get; set; }
}

class BoxEqualityComparer : IEqualityComparer<Box> {

    public bool Equals(Box b1, Box b2) {
        if (b1.Height == b2.Height & b1.Length == b2.Length
                            & b1.Width == b2.Width) {
            return true;
        }
        else {
            return false;
        }
    }

    public int GetHashCode(Box bx) {
        int hCode = bx.Height ^ bx.Length ^ bx.Width;
        return hCode.GetHashCode();
    }
}

Equals 方法实现不应该足以比较两个 Box 对象吗?这就是我们告诉框架用于比较对象的规则的地方。为什么需要 GetHashCode?​​p>

谢谢。

卢锡安

【问题讨论】:

标签: c# .net gethashcode iequalitycomparer


【解决方案1】:

先介绍一下背景...

.NET 中的每个对象都有一个 Equals 方法和一个 GetHashCode 方法。

Equals 方法用于将一个对象与另一个对象进行比较——看看这两个对象是否等价。

GetHashCode 方法生成对象的 32 位整数表示。由于一个对象可以包含多少信息没有限制,某些哈希码由多个对象共享 - 因此哈希码不一定是唯一的。

字典是一种非常酷的数据结构,它以更高的内存占用换取(或多或少)添加/删除/获取操作的恒定成本。但是,对于迭代来说,这是一个糟糕的选择。在内部,字典包含一个桶数组,其中可以存储值。将 Key 和 Value 添加到字典时,会在 Key 上调用 GetHashCode 方法。返回的 hashcode 用于确定存储 Key/Value 对的桶的索引。

当你想访问 Value 时,你再次传入 Key。 Key上调用GetHashCode方法,定位到包含Value的bucket。

当将 IEqualityComparer 传递到字典的构造函数时,将使用 IEqualityComparer.Equals 和 IEqualityComparer.GetHashCode 方法而不是 Key 对象上的方法。

现在要解释为什么这两种方法都是必要的,请考虑以下示例:

BoxEqualityComparer boxEqC = new BoxEqualityComparer(); 

Dictionary<Box, String> boxes = new Dictionary<Box, string>(boxEqC); 

Box redBox = new Box(100, 100, 25);
Box blueBox = new Box(1000, 1000, 25);

boxes.Add(redBox, "red"); 
boxes.Add(blueBox, "blue"); 

在您的示例中使用 BoxEqualityComparer.GetHashCode 方法,这两个框具有相同的哈希码 - 100^100^25 = 1000^1000^25 = 25 - 即使它们显然不是同一个对象。在这种情况下,它们是相同的哈希码的原因是因为您使用的是 ^(按位异或)运算符,因此 100^100 抵消了留下零,1000^1000 也是如此。当两个不同的对象具有相同的键时,我们称之为碰撞。

当我们将两个具有相同哈希码的键/值对添加到字典中时,它们都存储在同一个存储桶中。因此,当我们要检索一个 Value 时,会在 Key 上调用 GetHashCode 方法来定位存储桶。由于存储桶中有多个值,因此字典会遍历存储桶中的所有键/值对,并在键上调用 Equals 方法以找到正确的值。

在您发布的示例中,两个框是等效的,因此 Equals 方法返回 true。在这种情况下,字典有两个相同的 Key,因此会引发异常。

TLDR

所以综上所述,GetHashCode 方法用于生成存储对象的地址。所以字典不必搜索它。它只是计算哈希码并跳转到该位置。 Equals 方法是更好的相等性测试,但不能用于将对象映射到地址空间。

【讨论】:

  • 对于那些想知道 ^ 运算符是什么的人,这是按位异或运算符,请参阅msdn.microsoft.com/en-us/library/zkacc7k1.aspx。
  • 只是明确指出这一点:(msdn.microsoft.com/en-us/library/ms132155.aspx) 实现者注意事项实现需要确保如果 Equals 方法对两个对象 x 和 y 返回 true,则 GetHashCode 方法返回的值for x 必须等于为 y 返回的值。
  • @DiegoFrehner - 你说的很对。另一件会让人绊倒的事情是,如果对象被修改,GetHashCode 方法的值不应该改变。因此,GetHashCode 所依赖的对象中的字段应该是只读的(不可变的)。这里有解释:stackoverflow.com/a/4868940/469701
  • @Acentric:对象的哈希码不应该改变,除非它以影响平等的方式发生变异。如果可以以影响相等性的方式对类进行变异,则代码应避免在字典中存储任何可能暴露给在字典中时会对其进行变异的代码的实例。如果存储对象的代码遵守该规则,则具有反映可变状态的哈希码可能会很有用。太糟糕了 .NET 不能更好地区分状态相等和等价,因为两者都是有用的概念。
  • @Acentric:除了使用哈希码进行哈希表寻址之外,哈希码背后的基本思想是,知道两个对象具有不同的哈希码意味着它们' re 不相等,不需要比较它们。作为推论,知道许多对象的哈希码与给定对象的哈希码不匹配意味着它们都不等于该对象。使用哈希码进行寻址基本上是一种忽略具有不同哈希码的对象的方法。
【解决方案2】:

GetHashCode 用于 Dictionary 集合中,它会创建散列以在其中存储对象。这是一篇很好的文章为什么以及如何使用 IEqualtyComparer 和 GetHashCode http://dotnetperls.com/iequalitycomparer

【讨论】:

  • 更多:如果您需要比较 Equals 将是 enouf,但是当您需要从 Dictionary 中获取元素时,通过哈希而不是使用 更容易做到这一点等于.
【解决方案3】:

虽然Dictionary&lt;TKey,TValue&gt; 可以让其GetValue 和类似方法在每个存储的密钥上调用Equals 以查看它是否与正在寻找的密钥匹配,但这会非常慢。相反,像许多基于散列的集合一样,它依赖GetHashCode 快​​速排除大多数不匹配的值。如果对正在寻找的项目调用 GetHashCode 产生 42,并且一个集合有 53,917 个项目,但在 53,914 个项目上调用 GetHashCode 产生的值不是 42,那么只需将 3 个项目与正在搜索的项目进行比较寻求。其他 53,914 个可以放心地忽略。

GetHashCode 包含在IEqualityComparer&lt;T&gt; 中的原因是允许字典的使用者可能希望将其视为平等对象,而这些对象通常不 将彼此视为平等。最常见的示例是希望使用字符串作为键但使用不区分大小写的比较的调用者。为了使它有效地工作,字典需要有某种形式的散列函数,它将为“Fox”和“FOX”产生相同的值,但希望为“box”或“zebra”产生其他值。由于String 中内置的GetHashCode 方法不能以这种方式工作,因此字典需要从其他地方获取这样的方法,而IEqualityComparer&lt;T&gt; 是最合乎逻辑的地方,因为需要这样的哈希码是与Equals 方法密切相关,该方法认为“Fox”和“FOX”彼此相同,但与“box”或“zebra”不同。

【讨论】:

  • 问题的正确答案! GetHashCode() 必须为相关对象补充 Equals()。
  • @Sumith:很多关于哈希的讨论都谈到了桶,但我认为考虑排除更有用。如果比较代价高昂,即使使用未组织到存储桶中的集合,散列也可以带来好处。
猜你喜欢
  • 2011-08-20
  • 2014-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-23
  • 2016-10-10
相关资源
最近更新 更多