C#Dictionary中的KeyNotFoundException在根据内容更改属性值后,计算GetHashCode。

KeyNotFoundException in C# Dictionary after changing property value based on what, the GetHashCode is calculated. Why?

见下面的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
            static void Main(string[] args)
            {
    // Create Dictionary
                var dict = new Dictionary<TestClass, ValueClass>();

    // Add data to dictionary
                CreateSomeData(dict);

    // Create a List
                var list = new List<TestClass>();
                foreach(var kv in dict) {
    // Swap property values for each Key
    // For example Key with property value 1 will become 6
    // and 6 will become 1
                    kv.Key.MyProperty = 6 - kv.Key.MyProperty + 1;

    // Add the Key to the List
                    list.Add(kv.Key);
                }

// Try to print dictionary and received KeyNotFoundException.
                foreach (var k in list)
                {
                    Console.WriteLine($"{dict[k].MyProperty} - {k.MyProperty}");
                }
            }



    static void CreateSomeData(Dictionary<TestClass, ValueClass> dictionary) {
        dictionary.Add(new TestClass {MyProperty = 1}, new ValueClass {MyProperty = 1});
        dictionary.Add(new TestClass {MyProperty = 2}, new ValueClass {MyProperty = 2});
        dictionary.Add(new TestClass {MyProperty = 3}, new ValueClass {MyProperty = 3});
        dictionary.Add(new TestClass {MyProperty = 4}, new ValueClass {MyProperty = 4});
        dictionary.Add(new TestClass {MyProperty = 5}, new ValueClass {MyProperty = 5});
        dictionary.Add(new TestClass {MyProperty = 6}, new ValueClass {MyProperty = 6});
    }

键和值班:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace HashDictionaryTest
{
    public class TestClass
    {
        public int MyProperty { get; set; }

        public override int GetHashCode() {
            return MyProperty;
        }
    }

}

namespace HashDictionaryTest
{
    public class ValueClass
    {
        public int MyProperty { get; set; }

        public override int GetHashCode() {
            return MyProperty;
        }
    }

}

我在Ubuntu下使用DotNet核心2。我做了这个测试不只是好奇。然而,我的惊喜,我keynotfoundexception。

我收到了预期的值。然而,在一收到的上述异常。

我想知道的是,为什么我们有这样的错误呢?什么是最好的实践中生成的hash码,这样我们可以避免这样的问题吗?


What I want to know is, why we got this error?

有关于gethashcode的指南,也有规则。如果你违反了这些准则,你会得到糟糕的表现。如果你违反了规则,事情就会破裂。

你违反了规则。GetHashCode的规则之一是当对象在字典中时,其哈希代码不得更改。另一个规则是相等的对象必须具有相等的哈希代码。

你违反了规则,所以一切都被打破了。那是你的错,不要违反规则。

What is the best practice in generating the HashCode so that we can avoid such issues?

有关规则和指南的列表,请参阅:

Guidelines and rules for GetHashCode


这是代码的预期行为。那么您的代码有什么问题?

看看你的钥匙课。您将覆盖您的GetHashCode(),除此之外,您将使用可变值来计算GetHashCode()方法(非常糟糕:()。

1
2
3
4
5
6
7
8
public class TestClass
{
    public int MyProperty { get; set; }

    public override int GetHashCode() {
        return MyProperty;
    }
}

字典实现中的查找使用插入对象的GetHashCode()。在插入对象时,您的GetHashCode()返回了一些值,该对象被插入到了一些bucket中。但是,在您更改了您的MyProperty之后,GetHashCode()没有返回相同的值,因此无法再查找它。

在这里查找

1
Console.WriteLine($"{dict[k].MyProperty} - {k.MyProperty}");

dict[k]已经改变了它的MyProperty,因此当对象首次添加到字典时,GetHashCode()不会返回值。

另一件非常重要的事情是要记住,当您覆盖GetHashCode()时,也会覆盖Equals()。反之亦然!


keynotFoundException…为什么?

蒸馏的核心原因是EqualsGetHashCode方法不一致。这种情况是通过做两件事来解决的:

  • 覆盖TestClass中的Equals
  • 迭代期间从不修改字典
    • 关键对象/值正在修改

GetHashCodeEquals断开

  • …GetHashCode不应在对象的生命周期中更改。在计算GetHashCode()时,不要使用可能发生变化的值。
    • 不完全是。问题是在相等性不变的情况下更改哈希代码。
    • msdn说:对象的getHashCode()方法必须始终返回相同的哈希代码,只要不修改确定对象的System.Object.Equals方法返回值的对象状态。

埃多克斯1〔19〕

我说TestClass,因为那是字典的键。但这也适用于ValueClass

"EDOCX1"(12)和"EDOCX1"(13)类必须一致。当覆盖其中一个而不是两者时,则它们不一致。我们都知道"如果你覆盖Equals,也覆盖GetHashCode"。我们从来没有推翻过GetHashCode,但似乎已经摆脱了它。现在请听我说,相信我,当你第一次执行IEqualityComparerIEquatable时,一定要覆盖两者。

迭代字典

不要在迭代过程中添加或删除元素、修改键或修改值(有时)。

  • 正在编辑字典值…在一个循环中——它碰到了一些技术原因,在迭代期间无法安全地修改字典条目。
  • msdn字典文档
  • 可以在迭代期间更改值-字典"value",而不是键的值
  • 好吧,不是所有的情况

方法

  • msdn获取哈希代码
    • 不要将哈希代码用作从键控集合中检索对象的键。
    • 不要测试哈希代码是否相等以确定两个对象是否相等

操作代码可能不会从字面上做到这一点,但实际上肯定是因为没有等号重写。

这是C demi God Eric Lipper的一个简单哈希算法