Null propagation

Download c# eBook

Example

The ?. operator and ?[...] operator are called the null-conditional operator. It is also sometimes referred to by other names such as the safe navigation operator.

This is useful, because if the . (member accessor) operator is applied to an expression that evaluates to null, the program will throw a NullReferenceException. If the developer instead uses the ?. (null-conditional) operator, the expression will evaluate to null instead of throwing an exception.

Note that if the ?. operator is used and the expression is non-null, ?. and . are equivalent.


Basics

var teacherName = classroom.GetTeacher().Name;
// throws NullReferenceException if GetTeacher() returns null

View Demo

If the classroom does not have a teacher, GetTeacher() may return null. When it is null and the Name property is accessed, a NullReferenceException will be thrown.

If we modify this statement to use the ?. syntax, the result of the entire expression will be null:

var teacherName = classroom.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null

View Demo

Subsequently, if classroom could also be null, we could also write this statement as:

var teacherName = classroom?.GetTeacher()?.Name;
// teacherName is null if GetTeacher() returns null OR classroom is null

View Demo

This is an example of short-circuiting: When any conditional access operation using the null-conditional operator evaluates to null, the entire expression evaluates to null immediately, without processing the rest of the chain.

When the terminal member of an expression containing the null-conditional operator is of a value type, the expression evaluates to a Nullable<T> of that type and so cannot be used as a direct replacement for the expression without ?..

bool hasCertification = classroom.GetTeacher().HasCertification;
// compiles without error but may throw a NullReferenceException at runtime

bool hasCertification = classroom?.GetTeacher()?.HasCertification;
// compile time error: implicit conversion from bool? to bool not allowed

bool? hasCertification = classroom?.GetTeacher()?.HasCertification;
// works just fine, hasCertification will be null if any part of the chain is null

bool hasCertification = classroom?.GetTeacher()?.HasCertification.GetValueOrDefault();
// must extract value from nullable to assign to a value type variable

Use with the Null-Coalescing Operator (??)

You can combine the null-conditional operator with the Null-coalescing Operator (??) to return a default value if the expression resolves to null. Using our example above:

var teacherName = classroom?.GetTeacher()?.Name ?? "No Name";
// teacherName will be "No Name" when GetTeacher() 
// returns null OR classroom is null OR Name is null

Use with Indexers

The null-conditional operator can be used with indexers:

var firstStudentName = classroom?.Students?[0]?.Name;

In the above example:

  • The first ?. ensures that classroom is not null.
  • The second ? ensures that the entire Students collection is not null.
  • The third ?. after the indexer ensures that the [0] indexer did not return a null object. It should be noted that this operation can still throw an IndexOutOfRangeException.

Use with void Functions

Null-conditional operator can also be used with void functions. However in this case, the statement will not evaluate to null. It will just prevent a NullReferenceException.

List<string> list = null;
list?.Add("hi");          // Does not evaluate to null

Use with Event Invocation

Assuming the following event definition:

private event EventArgs OnCompleted;

When invoking an event, traditionally, it is best practice to check if the event is null in case no subscribers are present:

var handler = OnCompleted;
if (handler != null)
{
    handler(EventArgs.Empty);
}

Since the null-conditional operator has been introduced, the invocation can be reduced to a single line:

OnCompleted?.Invoke(EventArgs.Empty);

Limitations

Null-conditional operator produces rvalue, not lvalue, that is, it cannot be used for property assignment, event subscription etc. For example, the following code will not work:

// Error: The left-hand side of an assignment must be a variable, property or indexer
Process.GetProcessById(1337)?.EnableRaisingEvents = true;
// Error: The event can only appear on the left hand side of += or -=
Process.GetProcessById(1337)?.Exited += OnProcessExited;

Gotchas

Note that:

int? nameLength = person?.Name.Length;    // safe if 'person' is null

is not the same as:

int? nameLength = (person?.Name).Length;  // avoid this

because the former corresponds to:

int? nameLength = person != null ? (int?)person.Name.Length : null;

and the latter corresponds to:

int? nameLength = (person != null ? person.Name : null).Length;

Despite ternary operator ?: is used here for explaining the difference between two cases, these operators are not equivalent. This can be easily demonstrated with the following example:

void Main()
{
    var foo = new Foo();
    Console.WriteLine("Null propagation");
    Console.WriteLine(foo.Bar?.Length);

    Console.WriteLine("Ternary");
    Console.WriteLine(foo.Bar != null ? foo.Bar.Length : (int?)null);
}

class Foo
{
    public string Bar
    {
        get
        {
            Console.WriteLine("I was read");
            return string.Empty;
        }
    }
}

Which outputs:

Null propagation
I was read
0
Ternary
I was read
I was read
0

View Demo

To avoid multiple invocations equivalent would be:

var interimResult = foo.Bar;
Console.WriteLine(interimResult != null ? interimResult.Length : (int?)null);

And this difference somewhat explains why null propagation operator is not yet supported in expression trees.

Stats

Contributors: 30
2016-10-01
Licensed under: CC-BY-SA

Not affiliated with Stack Overflow
Rip Tutorial: info@zzzprojects.com

Download eBook