我理解不建议使用“可变”对象(GetHashCode()方法在Dictionary中用作键时)可以返回不同结果的对象。
以下是我对作为哈希表实现的字典如何工作的理解:
当我添加新密钥时,例如dict.Add(m1, "initially here was m1 object");
,dict
使用m1
方法计算GetHashCode()
的哈希码。然后它进行一些内部计算,最后将此对象放入其内部数组的某个位置。
当我使用密钥索引获取值时,例如dict[m1]
,dict
再次计算哈希码。然后它做了一些内部计算,它给了我一个位于其内部数组内部计算位置的对象。
但我认为有一个我找不到的错误。
所以我们假设我有这段代码:
class MutableObject
{
Int32 m_value;
public MutableObject(Int32 value)
{
m_value = value;
}
public void Mutate(Int32 value)
{
m_value = value;
}
public override int GetHashCode()
{
return m_value;
}
}
static void Main(string[] args)
{
MutableObject m1 = new MutableObject(1);
MutableObject m2 = new MutableObject(2);
var dict = new Dictionary<MutableObject, String>();
dict.Add(m1, "initially here was m1 object");
dict.Add(m2, "initially here was m2 object");
Console.WriteLine("Before mutation:");
Console.WriteLine("dict[m1] = " + dict[m1]);
Console.WriteLine("dict[m2] = " + dict[m2]);
m1.Mutate(2);
m2.Mutate(1);
Console.WriteLine("After mutation:");
Console.WriteLine("dict[m1] = " + dict[m1]);
Console.WriteLine("dict[m2] = " + dict[m2]);
Console.ReadKey(true);
}
当我调用Mutate
方法时,会交换密钥。所以我认为它会给出交换结果。但实际上这一行:Console.WriteLine("dict[m1] = " + dict[m1]);
抛出了KeyNotFoundException,我无法理解为什么。显然我在这里遗漏了一些东西......
答案 0 :(得分:8)
.NET Dictionary实现如何与可变对象一起使用
没有。 documentation for Dictionary州:
只要将对象用作
Dictionary<TKey, TValue>
中的键,就不得以任何影响其哈希值的方式进行更改。
由于您正在更改Dictionary
内的对象,因此无效。
至于为什么,这不是很难看。我们放了一个对象。我们假设哈希码是1
。我们将对象放在哈希表的1
桶中。现在,对象在Dictionary之外变异,因此它的值(和哈希码)是2
。现在,当有人将该对象提供给字典的索引器时,它会获取哈希码,看到它是2
,并查看2
桶。那个桶是空的,所以它说,“对不起,没有元素”。
现在让我们假设创建了一个新对象,其值和散列为1
。它被传递给Dictionary,后者看到哈希是1
。它在1
桶中查找并发现该索引确实存在一个项目。它现在使用Equals
来确定对象实际上是否相等(或者这只是一个哈希冲突)。
现在,在你的情况下,它会在这里失败,因为你没有覆盖Equals
,你正在使用比较引用的默认实现,因为这是一个不同的对象,它将不具有相同的参考。但是,即使您更改它以比较值,*第一个对象也会变异为2
,而不是1
,所以它无论如何都不会匹配。其他人建议修复此Equals
方法,你真的应该这样做,但它仍然无法修复你的问题。
一旦对象发生变异,找到它的唯一方法就是如果发生变异的值就是哈希冲突(这是可能的,但不太可能)。如果不是,那么根据Equals
的任何相同内容将永远不会知道检查正确的桶,并且根据Equals
检查正确桶的任何内容都不相等。
我在开始时提到的报价不仅仅是一种最佳做法。改变词典中的项目并不仅仅是出乎意料或奇怪或无法执行。 它只是不起作用。
现在,如果对象是可变的但是在字典中没有变异那么那很好。它可能有点奇怪,而且那个人们可能会说这是一种不好的做法,即使它有效。
答案 1 :(得分:1)
使用相同的哈希码进行字典查找是不够的。由于哈希冲突是可能的,因此密钥也必须等于正在查找的索引。
答案 2 :(得分:1)
您的MutableObject
课程未覆盖Equals(object)
。因此使用引用相等(继承自基类System.Object
)。
Dictionary<,>
首先(快速)找到具有正确哈希码的任何键。然后,它会检查每个候选键,以检查其中一个Equals
它正在搜索的键。
因此,Equals(object)
和GetHashCode()
应该被重写。如果您只覆盖其中一个,则会从编译器中获得警告。
当密钥的哈希码在密钥位于Dictionary<,>
时发生变异时,该密钥将(可能)在Dictionary<,>
内错位,在错误的“桶”中,并且因此迷失了。它将无法找到,因为搜索它将始终发生在它未找到的存储桶中。
在此示例中,密钥丢失,因此可以再次添加:
var dict = new Dictionary<MutableObject, string>();
var m = new MutableObject(1);
dict.Add(m, "Hello");
m.Mutate(2);
dict.Add(m, "world");
foreach (var p in dict)
Console.WriteLine(p);
var otherDict = new Dictionary<MutableObject, string>(dict); // throws
在使用现有Dictionary<,>
中的项目初始化一个Dictionary<,>
时,我实际上看到了类似的异常(两者都使用默认的EqualityComparer<>
作为密钥类型)。