为什么在添加到集合时不会为所有对象调用Equals()

时间:2013-02-20 14:07:15

标签: c# .net collections

我有一个类型,我在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方法。里面发生了什么。请解释一下。

5 个答案:

答案 0 :(得分:8)

字典和所有其他类似容器使用哈希码作为快速和脏检查:不同的哈希码意味着两个对象肯定不相等;相同的哈希码并不意味着什么。 GetHashCode的文档通过说

来指定此行为
  

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

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

答案 1 :(得分:1)

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

答案 2 :(得分:1)

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

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

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

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

答案 3 :(得分:1)

如果您继续此实验,您会发现某些特定于Dictionary<TKey, TValue>实施的行为,以及由于您实施GetHashCode的方式而需要的一些行为。

首先,在比较对象是否相等时,了解GetHashCodeEquals的作用非常重要。有关其他信息,请参见this question,但我将在此重复基本规则:

  1. Equals方法确切地确定哪些对象相等以及哪些对象不相等。在返回之前,需要在此方法中执行所有必要的检查以进行最终确定。
  2. 哈希码是根据对象的值计算的值。通常它比原始对象小得多(在我们的例子中,哈希码是4字节整数)并且不一定是唯一的。但是,与原始对象本身相比,计算和比较要快得多。
    • 当哈希码不需要是唯一的时,不同的哈希码表示不同的对象(即Equals肯定会返回false),但相等的哈希码并不意味着什么(即Equals可以返回true或者是假的。
  3. 将值与密钥对象(例如,.NET中的IDictionary<TKey, TValue>或Java中的Map<K, V>)相关联的集合利用哈希代码来提高实现效率。但是,由于Object.GetHashCode的文档特别不要求结果是唯一的,因此这些集合不能单独依赖哈希码来获得正确的功能。 当两个对象具有相同的哈希码时,只有对Equals的调用才能区分它们。您为emp3插入所描述的案例属于这种情况:[{如果您尝试插入相同的值,则{1}}]方法需要抛出IDictionary<TKey, TValue>.Add,并且只有对ArgumentException的调用才能确定新密钥Equals是否等于之前已插入emp3

    其他实施特征

    特定的集合实现可能会导致对emp3的调用次数超出预期。例如,当调整hash table的内部存储大小时,实现可能会为集合中存储的每个对象调用GetHashCode。基于binary-B-tree的集合可能只调用GetHashCode一次(如果结果缓存在树结构中),或者可能需要在多个对象期间调用GetHashCode每次插入或查找操作(如果结果未缓存)。

    有时哈希表实现需要为多个对象调用GetHashCode,或者甚至为具有不同哈希码的对象调用GetHashCode,因为他们使用模运算将键放入“桶”。具体特征因实施而异。

答案 4 :(得分:0)

这是因为 GetHashCode 是一种捷径。 C#将首先调用 GetHashCode ,它应该是快速执行的。 如果两个对象具有不同的HashCodes,那么就不需要调用更为昂贵的 Equals 方法。 只有当他们拥有相同的HashCode时,它才会调用 Equals 。这是因为HashCode不能保证是唯一的