Conditional member access operator (idiomatic uses).

4 minute read

Here are some of the less known uses of Null-conditional operator that could be handy to know.

1. use null-conditional access with void methods

A common misconception about null conditional operator is that it can be used only with members that return something. That is probably because of the special treatment where the return type is promoted to nullable (if it cannot represent null already).
It is, actually, perfectly fine to use null-conditional with a void returning method. The overall expression type is still void, just the method is called conditionally.

// get a dictionary if we have one
Dictionary<int, int> dict = GetDictionaryOrNull();

// add something, if we actually have a dictionary
// NOTE: Add has void return type
dict?.Add(42, GetValue());

It is particularly nice that execution of the wholeAdd(42, GetValue()) is conditional and thus GetValue() is only evaluated if dict is not null.
Without ?. the same code would not look as nice.

2. raising events

Generally C# events need to be null-checked before invoked. In addition to that, to be safe from races, an event needs to be captured into a local. That is quite a bit of code to just raise an event:

public class C
{    
    public event Action OnSomething = null;

    public void DoSomething()
    {
        // raise OnSomething, if not null
        var onSomething = OnSomething;
        if ( onSomething != null)
        {
           onSomething();         
        }
    }
}

Raising an event, however, is nothing more then invoking Invoke method on the event. And performing that conditionally on not being null is much clearer with ?. :

public class C
{    
    public event Action OnSomething = null;

    public void Something()
    {
        // raise OnSomething, if not null
        OnSomething?.Invoke();
    }
}

3. null-conditional and nul-coalescing operator together

When receiver of a null-conditional operator is null, the result is also null of appropriate type. That might be inconvenient in cases where a “default” result, other than null is supposed to be returned. That can be easily and elegantly fixed by combining ?. and ??.

// if obj is not null, give me its hashcode or 42 otherwise
int hashcode = obj?.GetHashCode() ?? 42;

It may seem that the code has two null checks here, which would be redundant, considering that only one input variable could be null:

// null check "obj", wrap result in "int?"
int? temp = obj?.GetHashCode();

// null check the "temp", unwrap "int?"
int hashcode = temp ?? 42;

Compiler is actually smart enough to understand the meaning of ?. + ?? combination, and emits more optimal code. It knows that the only way for the obj?.GetHashCode() to be null is when obj is null and in such case the whole expression returns 42. When obj is not null, the result of GetHashCode() is returned. In fact, there is no need to involve intermediate wrapping/unwrapping of int? at all.

The actual code, that is emitted, is an equivalent of:

object stackTemp = obj;
int hashcode = stackTemp != null ? stackTemp.GetHashCode() : 0;

4. use null-conditional in conditions

Null-conditional operator has type of bool?, when used with underlying expression of bool type. Such expression cannot be used directly in conditions. However, there are easy ways to “normalize” the three-state result in to true/false.

When null should be treated the same as false, use == true or ?? false.

public class C
{        
    // assigned externally
    public static HashSet<int> hs;

    public void Check()
    {		
        if (hs?.Contains(42) == true)
        {
            System.Console.WriteLine("contains");        
        }

        if (hs?.Contains(42) ?? false)
        {
            System.Console.WriteLine("contains");        
        }
    }
}

Both == true and ?? false result in the same code emitted as the resulting conditions indeed have the same semantics. In either case compiler can infer that the only situation in which the condition will be satisfied is when hs is not null and when hs.Contains(42) returns true.

I personally like == true form more, but I have seen ?? false used and I find it just as readable.

Again, the intermediate bool?, that would be produced by hs?.Contains(42) alone, and all expenses related to dealing with it, can be bypassed.

The actual codegen for either of the ifs above looks like:

IL_0000: ldsfld class [System.Core]System.Collections.Generic.HashSet`1<int32> C::hs
IL_0005: dup
IL_0006: brtrue.s IL_000c

IL_0008: pop
IL_0009: ldc.i4.0
IL_000a: br.s IL_0013

IL_000c: ldc.i4.s 42
IL_000e: call instance bool class [System.Core]System.Collections.Generic.HashSet`1<int32>::Contains(!0)

IL_0013: brfalse.s IL_001f

IL_0015: ldstr "contains"
IL_001a: call void [mscorlib]System.Console::WriteLine(string)

Which is an equivalent for an optimized:

HashSet<int> stackTemp = C.hs;
if (stackTemp != null && stackTemp.Contains(42))
{
    Console.WriteLine("contains");
}

Conversely != false and ?? true could be used when null is to be treated the same as true.

if (hs?.Contains(42) != false)
{
    System.Console.WriteLine("contains or null");        
}

if (hs?.Contains(42) ?? true)
{
    System.Console.WriteLine("contains or null");        
}

In either case, the condition is emitted as an optimized equivalent of:

HashSet<int> stackTemp = C.hs;
if (stackTemp == null || stackTemp.Contains(42))
{
    Console.WriteLine("contains or null");
}

5. composing null-conditional and lifted operators

Null-conditional operator mixes well with lifted operators.

public class C
{        
    // assigned externally
    public static string s;

    public bool IsLongEnough()
    {		
        return s?.Length * 2 + 1 > 10;
    }
}

Again, compiler knows about the short circuiting nature of ?. and that the only way a null can get into the calculation is through s being null and once that happens we immediately know the result without the need of propagating that null through the whole chain of lifted calculations.

The actual code emitted here is equivalent of:

public class C
{
    public static string s;
    public bool IsLongEnough()
    {
        string stackTemp = C.s;
        return stackTemp != null &&   // <- check for null once
                    stackTemp.Length * 2 + 1 > 10; // <- not null-propagating  
    }
}

As you may notice the intermediate nullables are again completely elided by the compiler and the math was simplified to regular (not the null-propagating) form.

Leave a Comment