在字典中查找项目基于哈希码。哈希代码基于以下代码中的项目ID,因此我不应该更改项目ID。但是我做了一些实验,更改了项目ID并在字典中进行了搜索,但随后出现KeyNotFoundException。有人可以解释一下为什么吗?
更改point1 id后,它具有1727699806哈希码,字典中索引为0的元素具有相同的哈希码:1727699806-那么为什么会有KeyNotFoundException?
class Program
{
public class Point
{
public int Id { get; set; }
public override bool Equals(object obj)
{
return obj is Point point &&
Id == point.Id;
}
public override int GetHashCode()
{
return HashCode.Combine(Id);
}
}
static void Main(string[] args)
{
Point point1 = new Point();
point1.Id = 5;
Point point2 = new Point();
point2.Id = 20;
var dictionary = new Dictionary<Point, string>()
{
{ point1, "Poland" },
{ point2, "Germany" }
};
point1.Id = 999; // here I change id
var point1_hash = point1.GetHashCode(); //1727699806
var dictionary1_hash = dictionary.ElementAt(0).Key.GetHashCode(); //1727699806
var dictionary2_hash = dictionary.ElementAt(1).Key.GetHashCode(); //650208270
string result = dictionary[point1]; //KeyNotFoundException
}
}
更改point1 id后,它具有1727699806哈希码,字典中索引为0的元素具有相同的哈希码:1727699806-那么为什么会有KeyNotFoundException?
答案 0 :(得分:2)
问题是对象/键放在Dictionary
类内的所谓“存储桶”中。存储桶中的每个条目都具有相同的哈希码(简化后,涉及一些模运算,因此没有int.MaxValue
存储桶)。当您使用键插入或检索对象时,Dictionary
类仅需要查看一个存储桶,因为键具有相同的哈希码。
但是现在您已经通过更改Id
属性来更改键对象的哈希码。密钥point1
现在存储在它不属于的存储桶中,但是Dictionary
类不知道该更改。这意味着:
Dictionary
类将出现在不同的存储桶中。Dictionary
类将找不到密钥/条目,因为它位于先前选择的存储桶(另一个桶)中。 因此,您不得将key
的对象更改为产生不同的哈希码,而将其用作Dictionary
中的键。参见documentation of the Dictionary
class:
只要将对象用作
Dictionary<TKey,TValue>
中的键,就不得以任何影响其哈希值的方式对其进行更改。
答案 1 :(得分:1)
此答案不正确,但由于技术原因无法删除。而是请参见下面的Progman答案。
答案 2 :(得分:0)
以下是Dictionary<T>
类中的相关source code:
private int FindEntry(TKey key)
{
if (buckets != null)
{
int hashCode = comparer.GetHashCode(key) & 0x7FFFFFFF;
for (int i = buckets[hashCode % buckets.Length]; i >= 0; i = entries[i].next)
{
if (entries[i].hashCode == hashCode && comparer.Equals(entries[i].key, key)) return i;
}
}
return -1;
}
如您所见,从计算键的哈希码开始搜索键。此哈希码确定将在桶中搜索匹配条目。然后,首先通过存储的哈希码比较指定存储桶中的每个条目,然后通过键的值进行比较。因此,更改存储在字典中的实体的键会以三种不同方式破坏键查找算法。