At the opening of this series I wrote about how a correct implementation of equality is essential for the correct behaviour of many fundamental .NET types - including
Dictionary. Here’s an example to show how they can break.
Consider this test code:
This test generates a large list of
Voter objects, puts them all into a
HashSet and then checks that all of the items in the set are really in the set.
Unsurprisingly, this test passes.
Let’s change the test slightly, by modifying one of the objects in the set after the set has been populated:
Now, the test fails. There’s something in the set that isn’t in the set. Oh dear.
What went wrong?
It’s important to note that this is not a bug with
HashSet<T>. Let me repeat that. It’s not a bug with
Instead, the problem is due to
Voter not correctly implementing the contract for equality and hash codes. Let’s look at the code.
Look at the implementations of
.GetHashCode(). Both methods use
PersonId and use
Vote. When the code in the second test modifies the vote of one of the voters, it changes the result of
.GetHashCode() and now the
HashSet can’t find the value.
In this case, we find that our
Voter class doesn’t really know what it is. If it was a proper entity type, we’d use only the
PersonId when evaluating
.GetHashCode(). If it was a proper value type,
Vote would be read-only and changing the value wouldn’t be possible.
Such a simple demo makes the problem easy to see. In real-world codebases that solve real business problems, the problem can exist without being anywhere this easy to observe.