Nullable<T>: Value vs GetValueOrDefault() in term of performance

 
 
  • Gérald Barré

Nullable types represent value-type variables that can be assigned the null value. You can use the Nullable<T>.HasValue and Nullable<T>.Value properties to test for null and retrieve the value, as shown in the following example:

C#
if (x.HasValue) // or x != null
{
    _ = x.Value;
}

In CoreFX issue about adding new analyzers in Microsoft.NetCore.Analyzers, Stephen Toub suggests to replace Nullable<T>.Value by Nullable<T>.GetValueOfDefault() for performance reasons:

After checking a Nullable<T>.HasValue, it's common to see calls to Nullable<T>.Value; instead of calling Value, it's less work to call GetValueOrDefault(), as Value repeats the HasValue check. It's possible a future JIT could optimize away the duplicate check, but if nothing else using GetValueOrDefault() makes the job of the JIT easier.

Note that Stephen Toub made this change in CoreCLR and CoreFx a few months ago: https://github.com/dotnet/coreclr/pull/22297. The comments are very interesting. Stephen Toub shows the differences in terms of generated assembly code and Andy Ayers explains a few things about the JIT.

Here's the code of Nullable<T>.Value and GetValueOfDefault() so you can see why GetValueOfDefault() should be faster:

C#
private readonly bool hasValue;
internal T value;

public T Value
{
    get
    {
        if (!hasValue)
        {
            ThrowHelper.ThrowInvalidOperationException_InvalidOperation_NoValue();
        }
        return value;
    }
}

public T GetValueOrDefault() => value;

So, if you have already check that the nullable has a value or if you are ok with the default value, you should use GetValueOrDefault instead of Value for performance reason.

C#
private int? _nullableInt = 10;

[Benchmark]
public int HasValue_Value()
{
    var nullableInt = _nullableInt;
    return nullableInt.HasValue ? nullableInt.Value : 0;
}

[Benchmark]
public int Coallesce()
{
    var nullableInt = _nullableInt;
    return nullableInt ?? 0;
}

[Benchmark]
public int GetValueOrDefault()
{
    var nullableInt = _nullableInt;
    return nullableInt.GetValueOrDefault();
}

Note that the performance of Nullable<T>.Value may vary depending on the branch predictor of the CPU whereas GetValueOrDefault() is stable.

The difference between the 3 implementations is really small. But if it's in the hot path such as in CoreFX or ASP.NET Core, every millisecond/nanosecond is important. If it's not the hot path, you can use the one you find the most explicit. I personally think that Value is more readable when it case just after HasValue.

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub