Reputation: 716
See the code below.
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});
}
Key and Value Class:
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;
}
}
}
I am using the dotnet core 2.2 on Ubuntu. I did this test out of curiosity only. However, to my surprise, I got KeyNotFoundException.
I expected to receive the wrong values. However, I received the exception as mentioned above.
What I want to know is, why we got this error? What is the best practice in generating the HashCode so that we can avoid such issues?
Upvotes: 2
Views: 519
Reputation: 5101
KeyNotFoundException ... Why?
The distilled, core reason is that the Equals
and GetHashCode
methods are inconsistent. This situation is fixed by doing 2 things:
Equals
in TestClass
GetHashCode
- Equals
disconnect
GetHashCode()
TestClass.Equals
I say TestClass
because that is the dictionary key. But this applies to ValueClass
too.
A class' Equals
and GetHashCode
must be consistent. When overriding either but not both then they are not consistent. We all know "if you override Equals
also override GetHashCode
". We never override GetHashCode
yet seem to get away with it. Hear me now and believe me the first time you implement IEqualityComparer
and IEquatable
- always override both.
Iterating Dictionary
Do not add or delete an element, modify a key, nor modify a value (sometimes) during iteration.
GetHashCode
The OP code may not be doing this literally but certainly virtually because there is no Equals override.
Here is a neat hash algorithm from C# demi god Eric Lipper
Upvotes: 2
Reputation: 660503
What I want to know is, why we got this error?
There are guidelines for GetHashCode, and there are rules. If you violate the guidelines, you get lousy performance. If you violate the rules, things break.
You are violating the rules. One of the rules of GetHashCode is while an object is in a dictionary, its hash code must not change. Another rule is that equal objects must have equal hash codes.
You've violated the rules, and so things are broken. That's your fault; don't violate the rules.
What is the best practice in generating the HashCode so that we can avoid such issues?
For a list of the rules and guidelines, see:
https://ericlippert.com/2011/02/28/guidelines-and-rules-for-gethashcode/
Upvotes: 5
Reputation: 3691
That is the expected behavior with your code. Then what is your wrong with your code?
Look at your Key Class. You are overriding your GetHashCode()
and on top of that you are using a mutable value to calculate the GetHashCode()
method (very very bad :( ).
public class TestClass
{
public int MyProperty { get; set; }
public override int GetHashCode() {
return MyProperty;
}
}
The look up in the implementation of the Dictionary uses GetHashCode()
of the inserted object. At the time of insertion of your object your GetHashCode()
returned some value and that object got inserted into some bucket
. However, after you changed your MyProperty
; GetHashCode()
does not return the same value therefore it can not be look-ed up any more
This is where the lookup occurs
Console.WriteLine($"{dict[k].MyProperty} - {k.MyProperty}");
dict[k]
already had its MyProperty
changed therefore GetHashCode()
does not return the value when the object first added to the dictionary.
Ant another really important thing is to keep in mind that when you override GetHashCode()
then override Equals()
as well. The inverse is true too!
Upvotes: 4