C# 8 - Null coalescing/compound assignment

There are a lot of cool new features in C# 8 and one of my favorites is the new Null coalescing assignment operator.

C# 8 -  Null coalescing/compound assignment

Hi friends.

There are a lot of cool new features in C# 8 and one of my favorites is the new Null coalescing assignment (or compound assignment, whichever you prefer) operator.

If you have ever written code like this:

private string _someValue;

public string SomeMethod()
{
    // Let's do an old-school null check and initialize if needed.
    if(_someValue == null)
    {
        _someValue = InitializeMyValue();
    }
    
    return _someValue;
}

private string InitializeMyValue()
{
    // Do some expensive initialization that we only want to do once...
}

You can simplify this code A LOT!

private string _someValue;

public string SomeMethod()
{
    // Using the awesome new ??= operator to do all in one go!
    _someValue ??= InitializeMyValue();
    return _someValue;
}

private string InitializeMyValue()
{
    // Do some expensive initialization that we only want to do once...
}

That is the new ??= operator in action, which takes care of doing the null check and assignment for you in one sweet syntactic sugar-rush! I also find it more readable, but some people might disagree, and that fine. Just pick whatever you prefer :)

And the best part is that it actually compiles down to the same efficient code! Just take a look at this sharplab.io sample to see what I mean.

You might have solved this with a Lazy<string> or by using something like _someValue = _someValue ?? InitializeMyValue(); as well but that's still more code to write than the new operator and less efficient as well. The Lazy<T> approach has some overhead and the null-coalescing operator + assignment has the added inefficiency of always making the variable assignment even if there is no need to (you can take a closer look at the ASM part of the SharpLab example above) but there it is for reference:

NullVsCoalescingVsCompound.NullCheckAndAssign()
    L0000: cmp dword [ecx+0x4], 0x0
    L0004: jnz L0014
    L0006: mov eax, [0xeb52038]
    L000c: lea edx, [ecx+0x4]
    L000f: call 0x6fe391d0
    L0014: mov eax, [ecx+0x4]
    L0017: ret

NullVsCoalescingVsCompound.CoalescingOperatorAndAssign()
    L0000: mov eax, [ecx+0x4]
    L0003: test eax, eax
    L0005: jnz L000d
    L0007: mov eax, [0xeb52038]
    L000d: lea edx, [ecx+0x4]
    L0010: call 0x6fe391d0
    L0015: mov eax, [ecx+0x4]
    L0018: ret

NullVsCoalescingVsCompound.CompoundAssignment()
    L0000: cmp dword [ecx+0x4], 0x0
    L0004: jnz L0014
    L0006: mov eax, [0xeb52038]
    L000c: lea edx, [ecx+0x4]
    L000f: call 0x6fe391d0
    L0014: mov eax, [ecx+0x4]
    L0017: ret

NullVsCoalescingVsCompound.LazyInitializer()
    L0000: push ebp
    L0001: mov ebp, esp
    L0003: mov ecx, [ecx+0x8]
    L0006: cmp dword [ecx+0x4], 0x0
    L000a: jz L0013
    L000c: call System.Lazy`1[[System.__Canon, System.Private.CoreLib]].CreateValue()
    L0011: jmp L0016
    L0013: mov eax, [ecx+0xc]
    L0016: pop ebp
    L0017: ret

NullVsCoalescingVsCompound.GetValue()
    L0000: mov eax, [0xeb52038]
    L0006: ret

Hope this helps! :)

Mastodon