.NET Dictionary实现如何与可变对象一起使用

时间:2013-03-12 19:12:42

标签: c# .net data-structures hashtable

我理解不建议使用“可变”对象(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,我无法理解为什么。显然我在这里遗漏了一些东西......

3 个答案:

答案 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<>作为密钥类型)。