Are we guaranteed that hash caching through data race will work correctly?

public class TestHashRace { private int cachedHash = 0; private readonly object value; public object Value { get { return value; } } public TestHashRace(object value) { this.value = value; } public override int GetHashCode() { if (cachedHash == 0) { cachedHash = value.GetHashCode(); } return cachedHash; } //Equals isn't part of the question, but since comments request it, here we go: public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != GetType()) return false; return Equals((TestHashRace) obj); } protected bool Equals(TestHashRace other) { return Equals(value, other.value); } } 

Here's a simple test class.

Do we guarantee that GetHashCode will always return the same value? And if so, can anyone point out some background material that gives us this guarantee?

We don’t worry if it calculates a hash code for our value more than once, we just want to be sure that the return value will always be the same.

Our class must be immutable, and the cachedHash field is mutable. The field cannot be unstable for performance reasons (the whole idea of ​​this question and the optimization we ask here). The value is unchanged. And it must be thread safe.

We can live with a potential hash recount when it is 0 for some values. We don’t want to use types with a null value or add additional fields for memory reasons (less memory is used if we save only 1 int), therefore there must be one int field to handle the hashcode problem.

+7
concurrency volatile memory-model
source share
3 answers

Do we guarantee that GetHashCode will always return the same value?

Not. The guarantee applies only to immutable value objects with the correctly implemented GetHashCode method. Mutable objects can change their hash code when their contents have been mutated (which is the reason that mutable objects should not be used as hash keys).

This is true even if TestHashRace itself is immutable, because you can do this:

 var evil = new StringBuilder("hello"); var thr = new TestHashRace(evil); RunConcurrentCode(thr); evil.Append(", world!"); 

If multiple threads in RunConcurrentCode start a call to thr GetHashCode at the same time and then terminate on different sides of the Append , the number returned from value.GetHashCode may be different.

[ Edit: ] The value is unchanged

Then the only thing required for the guarantee is that the value GetHashCode correctly implemented, i.e. does not use random material, etc.

Note: Since null is a valid value for a hash code, your code may repeatedly call value GetHashCode when the actual code is zero. One way to fix this would be to use a value with a null cachedHash value:

 int? cachedHash; ... public override int GetHashCode() { return cachedHash ?? (cachedHash = value.GetHashCode()); } 
+6
source share

No, it is not, because 0 is the actual result for value.GetHashCode() . Make cacheedHash a null int and check for null instead of 0.

+3
source share

There is no guarantee, because you can go and implement the class using the GetHashCode method, which does arbitrary stupid things. The compiler will not stop you from doing this.

Another question: can you expect GetHashCode always return the same value. The answer to this question is yes, basically. This is a design decision. However, for most classes, the ability to use instances as a key in a dictionary is important for implementing GetHashCode so that the value never changes, for example, without overlapping it, or only redefining it to save reflection costs.

Remarkably, this includes StringBuilder , so the race condition marked by dasblinkenlight does not really exist: unlike String , StringBuilder will always return the same hash code.

So why basically? The answer to this is a bit uncomfortable. Technically, the String class is not immutable. There are some evil (i.e., unsafe) ways to change the contents of a string without changing the link, which in turn will lead to different hash codes for the same link. You will also find a number of people implementing values ​​like Equals and GetHashCode for classes that suffer from the same problem (and you don't need to use unsafe code to get into the problem).

Thus, there is no guarantee, but this is a fair assumption. Just document this assumption so that users of your code do not run into difficulties, and everything should be in order.

-one
source share

All Articles