C# 带有字典和哈希集的GetHashCode方法

C# 带有字典和哈希集的GetHashCode方法,c#,.net,dictionary,hashset,C#,.net,Dictionary,Hashset,我有一个关于C#中字典和哈希集如何工作的问题。根据我的理解,在哈希表中使用GetHashCode来确定键的唯一性 在以下MSDN页面上,它指出: 哈希代码是一个数值,用于在基于哈希的集合(如字典类、哈希表类或从DictionaryBase类派生的类型)中插入和标识对象 链接: 如果是这种情况,为什么ContainsKey和Contains在car2的哈希代码与car1相同时返回false?如果我的理解是正确的,如果MSDN所说的是正确的,那么这两种说法不都是正确的吗 class Program

我有一个关于C#中字典和哈希集如何工作的问题。根据我的理解,在哈希表中使用GetHashCode来确定键的唯一性

在以下MSDN页面上,它指出:

哈希代码是一个数值,用于在基于哈希的集合(如字典类、哈希表类或从DictionaryBase类派生的类型)中插入和标识对象

链接:

如果是这种情况,为什么ContainsKey和Contains在car2的哈希代码与car1相同时返回false?如果我的理解是正确的,如果MSDN所说的是正确的,那么这两种说法不都是正确的吗

class Program
{
    static void Main(string[] args)
    {            
        // Create a Dictionary and HashSet
        Dictionary<Car, int> carDictionary = new Dictionary<Car, int>();
        HashSet<Car> carSet = new HashSet<Car>();

        // Create 3 Cars (2 generic and 1 Civic)
        Car car1 = new Car();
        Car car2 = new Car();
        Car car3 = new Civic();

        // Test hash values
        int test1 = car1.GetHashCode(); // 22008501
        int test2 = car2.GetHashCode(); // 22008501
        int test3 = car3.GetHashCode(); // 12048305


        // Add 1 generic car and 1 Civic to both Dictionary and HashSet
        carDictionary.Add(car1, 1);
        carDictionary.Add(car3, 1);
        carSet.Add(car1);
        carSet.Add(car3);

        // Why are both of these false?
        bool dictTest1 = carDictionary.ContainsKey(car2);  // false
        bool setTest1 = carSet.Contains(car2); // false

        // Testing equality makes sense
        bool testA = car1.Equals(car2); // false
        bool testB = car1.Equals(car3); // false
    }
}

class Car
{
    public override int GetHashCode()
    {
        return 22008501;
    }
}

class Civic : Car
{
    public override int GetHashCode()
    {
        return 12048305;
    }
}
类程序
{
静态void Main(字符串[]参数)
{            
//创建字典和哈希集
字典carDictionary=新字典();
HashSet carSet=新HashSet();
//创建3辆车(2辆普通车和1辆思域车)
Car car1=新车();
Car car2=新车();
Car car3=新思域();
//测试散列值
int test1=car1.GetHashCode();//22008501
int test2=car2.GetHashCode();//22008501
int test3=car3.GetHashCode();//12048305
//向Dictionary和HashSet添加1个generic car和1个Civic
添加(car1,1);
添加(car3,1);
carSet.Add(car1);
carSet.Add(car3);
//为什么这两个都是假的?
bool dictTest1=carDictionary.ContainsKey(car2);//false
bool setTest1=carSet.Contains(car2);//false
//测试平等性是有意义的
bool testA=car1.Equals(car2);//false
bool testB=car1.Equals(car3);//false
}
}
班车
{
公共覆盖int GetHashCode()
{
返回22008501;
}
}
类别:私家车
{
公共覆盖int GetHashCode()
{
返回12048305;
}
}

不必保证哈希代码是唯一的,如果键相等,则它们必须相等

现在发生的是,这些项目存储在桶中。如果查询
字典
是否包含给定的键或
哈希集
给定的项,它将首先计算哈希码以获取正确的bucket

接下来,它将对bucket中的所有项进行迭代,并对其执行
.Equals
测试。只有在其中一个匹配的情况下,才会返回
true

换句话说,允许一个为每个实例返回相同的hashcode,尽管实例不同。这只会使散列效率低下

因此,C#存储一个
字典
,如:

+----------+
| 22008501 |---<car1,1>----<car3,1>----|
+----------+
| 11155414 | (other bucket)
+----------+
+----------+
| 22008501 |-----------|
+----------+
|11155414 |(其他铲斗)
+----------+
左侧有(可能的bucket标签),尽管对于较小的
字典
,bucket的数量将非常小,并且将对散列(例如模)执行操作,以使结果的数量更小


现在,如果您查询
car2
是否在
字典中,它将计算哈希值,从而获取第一个bucket。然后它将迭代,并对
car1
vs
car2
执行相等检查,然后
car3
vs
car2
,它将到达存储桶的末尾并返回
false
。这是因为默认的
Equals
操作是reference equality。只有当您
也覆盖了
(例如,所有汽车都是相同的,您可以返回
true
)。

正如您所注意到的,
car1.Equals(car2)
不是真的
Dictionary
Hashset
成员资格仅适用于相等的对象。这意味着
.Equals()
返回true。只有当它们的hashcode第一次被发现相等时,才会对其进行测试。

,因为ContainsKey的逻辑与此类似

//This is a simplified model for answering the OP's question, the real one is more complex.
private List<List<KeyValuePair<TKey,TValue>>> _buckets = //....

public bool ContainsKey(TKey key)
{
    List<KeyValuePair<TKey,TValue>> bucket = _buckets[key.GetHashCode() % _buckets.Length];
    foreach(var item in bucket)
    {
        if(key.Equals(item.Key))
            return true;
    }
    return false;
}

这与哈希冲突有关吗?它们必须具有相同的哈希代码并且相等。您还应该覆盖
object.Equals
。您可能有兴趣查看Jon Skeet的答案。这可以回答您的问题:谢谢,这为我澄清了问题。我明白了。我理解equality方法,但不理解字典和HashSet是如何实现Contains/ContainsKey方法的。正确的实现可在的参考源页面中找到。
private int[] m_buckets;
private Slot[] m_slots;

public bool Contains(T item) {
    if (m_buckets != null) {
        int hashCode = InternalGetHashCode(item);
        // see note at "HashSet" level describing why "- 1" appears in for loop
        for (int i = m_buckets[hashCode % m_buckets.Length] - 1; i >= 0; i = m_slots[i].next) {
            if (m_slots[i].hashCode == hashCode && m_comparer.Equals(m_slots[i].value, item)) {
                return true;
            }
        }
    }
    // either m_buckets is null or wasn't found
    return false;
}

private int InternalGetHashCode(T item) {
    if (item == null) {
        return 0;
    } 
    return m_comparer.GetHashCode(item) & Lower31BitMask;
}

internal struct Slot {
    internal int hashCode;      // Lower 31 bits of hash code, -1 if unused
    internal T value;
    internal int next;          // Index of next entry, -1 if last
}