12

我有一个类型,我在 IDictionary 中用作键。类型如下

public  class Employee
{
    public string Name { get; set; }
    public int ID { get; set; }

    public override bool Equals(object obj)
    {
        Employee emp = obj as Employee;
        if (emp != null)
            return emp.Name.Equals(this.Name);
        return false;
    }

    public override int GetHashCode()
    {
        return this.Name.GetHashCode();
    }
}

现在我在我的主目录中创建了一个字典,如下所示

 IDictionary<Employee, int> empCollection = new Dictionary<Employee, int>();
        Employee emp1 = new Employee() { Name = "abhi", ID = 1 };
        Employee emp2 = new Employee() { Name = "vikram", ID = 2 };
        Employee emp3 = new Employee() { Name = "vikram", ID = 3 };

        empCollection.Add(emp1, 1);
        empCollection.Add(emp2, 2);
        empCollection.Add(emp3, 3);

现在在调试时我发现当将emp1添加到集合中时,仅调用键类型的GetHashCode方法,之后当将emp2添加到集合中时,仅再次调用GetHashCode方法,但在emp3的情况下,GetHashCode和Equals方法都被调用被称为。

问这个问题可能看起来太天真了,但是为什么在将 eqImp2 对象添加到集合时不调用 Equals 方法。里面发生了什么。请解释。

4

5 回答 5

8

字典和所有其他类似的容器都使用哈希码作为快速检查:不同的哈希码意味着两个对象绝对不相等;相同的哈希码没有任何意义。的文档GetHashCode通过说来指定这种行为

如果两个对象比较相等,则每个对象的 GetHashCode 方法必须返回相同的值。但是,如果两个对象比较不相等,则两个对象的 GetHashCode 方法不必返回不同的值。

您的emp1emp2生成不同的哈希码,因此不需要运行字典Equals;它已经知道它们是不平等的。另一方面,emp2emp3​​生成相同的哈希码,因此字典必须调用Equals以确定它们是否确实相等,或者相同的哈希码是否只是偶然的结果。

于 2013-02-20T14:10:40.597 回答
1

在您的示例中,GetHashCode查看名称哈希码。emp3 与 emp2 同名,(“vikram”)。给定哈希码,它们是相等的,因此使用Equals.

于 2013-02-20T14:11:45.997 回答
1

emp2 和 emp3 具有相同的密钥。这将导致字典中的“键冲突”。它首先调用 GetHashCode() 并确定哈希码相同。然后它通过调用 Equals() 来确保它们相同。字典中的代码是:

int num = this.comparer.GetHashCode(key) & 2147483647;
...
if (this.entries[i].hashCode == num && this.comparer.Equals(this.entries[i].key, key))

显然,如果哈希码不匹配,它永远不必调用 Equals。

你应该得到一个像 ILSpy 这样的工具,然后你可以查看代码并自己找到答案。

于 2013-02-20T14:12:37.687 回答
1

如果您继续这个实验,您将观察到一些特定于Dictionary<TKey, TValue>实现的行为,以及由于您实现的方式而需要的一些行为GetHashCode

首先,重要的是要了解在比较对象是否相等时的作用GetHashCode和时间。有关此问题Equals的其他信息可用,但我将在此处重复基本规则:

  1. Equals方法准确地确定了哪些对象相等,哪些对象不相等。需要在此方法中执行所有必要的检查,以便在返回之前进行最终确定。
  2. 哈希码是根据您的对象的值计算得出的值。通常它比原始对象小得多(在我们的例子中,哈希码是一个 4 字节的整数)并且不一定是唯一的。然而,计算和相互比较比原始对象本身要快得多。
    • 当哈希码不需要唯一时,不同的哈希码表示不同的对象(即Equals肯定会返回假),但相同的哈希码并不意味着什么(即Equals可以返回真或假)。

将值与关键对象相关联的集合(例如IDictionary<TKey, TValue>在.NET 或Map<K, V>Java 中)利用散列码来提高实现效率。但是,由于Object.GetHashCode特定的文档不要求结果是唯一的,因此这些集合不能单独依赖哈希码来获得正确的功能。当两个对象具有相同的哈希码时,只有调用Equals才能区分它们。您描述的插入的情况emp3属于这种情况:如果您尝试插入相同的值, [ IDictionary<TKey, TValue>.Add] 方法需要抛出 an ,并且只有调用才能确定新键是否等于先前插入的。ArgumentExceptionEqualsemp3emp3

其他实现特征

特定的集合实现可能会导致GetHashCode比您预期更多的调用。例如,当哈希表的内部存储被调整大小时,实现可能会调用GetHashCode存储在集合中的每个对象。基于二叉树或 B 树的集合可能调用一次GetHashCode(如果结果缓存在树结构中),或者可能需要GetHashCode在每次插入或查找操作期间调用多个对象(如果结果未缓存)。

有时哈希表实现需要调用GetHashCode多个对象,甚至可能Equals需要具有不同哈希码的对象,因为它们使用模算术将键放入“桶”中。其具体特征因一种实现方式而异。

于 2013-02-20T14:39:48.140 回答
0

那是因为GetHashCode是一个快捷方式。C# 将首先调用应该快速执行的GetHashCode 。如果两个对象具有不同的 HashCode,则无需调用假定更昂贵的Equals方法。只有当它们具有相同的 HashCode 时,它​​才会调用Equals。那是因为 HashCode 不保证是唯一的

于 2013-02-20T14:11:22.983 回答