Nullable types arithmetic and null-coalescing operator precedence

Sergey Tepliakov


Here is a simple question for you: which version of a GetHashCode() is correct and what performance impact does the incorrect version have?

The structs are not perfect (they don’t implement IEquatable<T>) but this is not the point. The only difference between the two is the GetHashCode() implementation:

Let’s check the behavior using the following simple benchmark:

The results are:

Wow! The Struct2 is 2000 times faster! This definitely means that the second implementation is correct and the first one is not! Right? Actually, not.

Both implementations are incorrect and just by an accident the second one “works better” in this particular case. Let’s take closer look at the GetHashCode method for Struct1:

You may think that this statement is equivalent to N ^ (S?.GetHashCode() ?? 0)but it is actually equivalent to (N ^ S?.GetHashCode()) ?? 0:

Now it is way more obvious why the Struct1 is so slow: when S property is null(which is always the case in this example), the hash code is 0 regardless of the Nbecause N ^ (int?)null is null. And trying to add 10000 values with the same hash code effectively converts the hash set into a linked list drastically affecting the performance.

But the second implementation is also wrong:

Is equivalent to:

In this particular case, this implementation gives us way better distribution, but just because the S is always null. In other scenarios, this hash function could be terrible and could give the same value for a large set of instances as well.


There are two reasons why the expression N ^ S?.GetHashCode()??0 gives us not what we could expect. C# supports the notion of lifted operators that allows mixing nullable and non-nullable values together in one expression: 42 ^ (int?)null is null. Second, the priority of null-coalescing operator (??) is lower than the priority of ^.

Operator precedence for some operators is so obvious that we can omit explicit parens around them. In case of the null-coalescing operator, the precedence could be tricky so use parenthesis to clarify your meaning.

Additional references

Sergey Tepliakov
Sergey Tepliakov

Senior Software Engineer, Tools for Software Engineers

Follow Sergey   


    Leave a comment