C# 6.0C# 6.0 introduced two new null-propagation operators: ?. and ?[]. They will make null reference check much easier. In this article, we will see how they work and how they implemented internally.

We all know about NullReferenceException and how to avoid it in our code. We just need to check everything for null before accessing some fields\properties\methods.

Null Propagation Operator ?.

var str = GetString();
if (str != null)
{
    return str.Length;
}
else
{
    return 0;
}

Now we can use Null Propagation operator:

var str = GetString();
return str?.Length ?? 0;

This operator is just a syntax sugar for null reference check. If operand is null, operator returns null and does not execute right part of the expression. If left operand is not null it will return result from the right expression (after .). In our example, it will return Length if str is not null.

Let's look at the other example to see how it work with longer chain:

var str = GetString();
var typeName = str?.Length.GetType().Name;

if str is null, the whole expression after operator will be ignored (Length.GetType().Name is ignored if str is null).

It might be very helpful if you have a chain of methods to invoke and do not want to do a lot of null checks.

var user = repository.GetUser();
var street = user?.Address?.Street;

First it will check if user is null and if not will check if Address is null and then will return Street value. If user is null it will immediately return null and will not perform null check for Address property.

The other question is what is the result type of this operator? In case of str?.Length we will get null if str is null, but Length is of an Integer type. The answer is: Nullable<int>.

If type of the expression is Value Type it will be wrapped in Nullable<> value type.

Let's check it with a code:

var str = "string";
Console.WriteLine((str?.Length.GetType().Name) is string);
// True

var str2 = "string";
Console.WriteLine((str2?.Length) is Nullable<int>);
// True

As I said, this is just a syntax sugar for if/else construction. Let's check one more example and see resulted IL code.

var str = GetString();
Console.WriteLine((str?.Length)?.GetType().Name);

IL code for this:

  .method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    // Code size       74 (0x4a)
    .maxstack  1
    .locals init ([0] string str,
             [1] valuetype [mscorlib]System.Nullable`1<int32> V_1,
             [2] valuetype [mscorlib]System.Nullable`1<int32> V_2)
    IL_0000:  nop
    IL_0001:  call       string ConsoleApplication1.Program::GetString()
    IL_0006:  stloc.0
    IL_0007:  ldloc.0
    IL_0008:  brtrue.s   IL_0015

    IL_000a:  ldloca.s   V_2
    IL_000c:  initobj    valuetype [mscorlib]System.Nullable`1<int32>
    IL_0012:  ldloc.2
    IL_0013:  br.s       IL_0020

    IL_0015:  ldloc.0
    IL_0016:  call       instance int32 [mscorlib]System.String::get_Length()
    IL_001b:  newobj     instance void valuetype [mscorlib]System.Nullable`1<int32>::.ctor(!0)
    IL_0020:  stloc.1
    IL_0021:  ldloca.s   V_1
    IL_0023:  call       instance bool valuetype [mscorlib]System.Nullable`1<int32>::get_HasValue()
    IL_0028:  brtrue.s   IL_002d

    IL_002a:  ldnull
    IL_002b:  br.s       IL_0043

    IL_002d:  ldloca.s   V_1
    IL_002f:  call       instance !0 valuetype [mscorlib]System.Nullable`1<int32>::GetValueOrDefault()
    IL_0034:  box        [mscorlib]System.Int32
    IL_0039:  call       instance class [mscorlib]System.Type [mscorlib]System.Object::GetType()
    IL_003e:  callvirt   instance string [mscorlib]System.Reflection.MemberInfo::get_Name()
    IL_0043:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_0048:  nop
    IL_0049:  ret
  } // end of method Program::Main

Which is more or less the same as following C# code:

var str = GetString();
Nullable<int> result;

if (str != null)
{
    var nullable = new Nullable<int>(str.Length);
    if (nullable.HasValue)
    {
        var integer = nullable.GetValueOrDefault();
        var length = integer.GetType().Name.Length;
        result = length;
    }
    else
    {
        result = new int?();
    }
}
else
{
    result = new int?();
}

Console.WriteLine(result);

Indexer Null Propagation Operator ?[]

Sometimes we need to check if collection is not null and then take some item from the collection:

List<int> list = GetList();
if (list != null)
{
    Console.WriteLine(list[1]);
}

Now we can use indexer null propagation operator:

List<int> list = GetList();
Console.WriteLine(list?[0]);

Logic is the same. It will check if list is null, if it is null operator just return null (in our case Nullable<int>), otherwise it will return item with index 0.

This operator has one misunderstanding, if you have list of objects it is hard to see what is null, list or item in list.

Use cases

It is useful to combine null-propagation operator with null-coalescing operator (??). As in first example:

return user?.Address?.Street ?? "Unknown";

The seconds case if null check for delegates. This one:

var handler = SomeEvent;
if (handler != null)
{
    handler(new Args());
}

Could be replaced with:

SomeEvent?.Invoke(new Args());