If you have been programming in C# for any length of time, I’m sure you’ve come across the infamous NullReferenceException. Over the years, newer versions of C# added newer constructs to enable better ergonomics in checking for null values and even more recently, newer constructs to avoid nulls in your application altogether. Let’s look at some of the ways we can deal with nulls in our C# code.
The Humble Null Check
The most basic null check that exists in C# looks something like this:
string x = GetSomething(); if(x == null) { Console.WriteLine("x was null"); } else { Console.WriteLine($"x = {x}"); } static string GetSomething() { var second = DateTime.UtcNow.Second; if(second % 2 == 0) { return second.ToString(); } return null; }
In the example above, I have created a GetSomething
method that either returns a number or a null, depending on the exact moment the code was executed. If the current second
value in the current time is an even number, it returns that second value; otherwise, it returns a null. In this example, the null check is: if(x == null)
.
Pattern Matching
Pattern Matching, a feature that was introduced in C# 7, made part of the null check a bit more readable:
if(x is null) { Console.WriteLine("x was null"); }
However, you couldn’t do the inverse, if(x is not null)
until C# 9 came out. Before that you had to use the clunky if(!(x is null))
to do the inverse.
Null Coalescing Operator
The null coalescing operator allows you to check for a null value and do an assignment to a variable in a single line. Consider the example below:
string x = GetSomething() ?? "x was null"; Console.WriteLine(x);
The ??
in the snippet above checks to see if the GetSomething() method returns a non-null value. If so, it will assign that returned value to string x
. Otherwise, it will assign the string literal x was null
to that variable, instead.
Null Propagation
When you have an object property, especially several levels deep, it gets quite cumbersome to check if each level is not null and only if the current level is null, proceed to check and obtain the value from the next level. Consider the example below:
public static class Program { public static void Main() { Person tom = new Person { Address = new Address { PostalCode = new PostalCode { ZipCode = "11427", ZipExtension = "1234" } } }; var tomsZipCode = string.Empty; if(tom is not null) { if(tom.Address is not null) { if(tom.Address.PostalCode is not null) { if(tom.Address.PostalCode.ZipCode is not null) { tomsZipCode = tom.Address.PostalCode.ZipCode; } } } } Console.WriteLine(tomsZipCode); } } public class Person { public Address? Address { get; set; } } public class Address { public PostalCode? PostalCode { get; set; } } public class PostalCode { public string? ZipCode { get; set; } public string? ZipExtension { get; set; } }
You’ll hopefully agree with me that the code above is quite ugly. On the other hand, it is imperative that we check for nulls at each level because if any of those levels are null and we try to access the value, it will throw a NullReferenceException. Null Propagation helps in handling the above situation in a much more succinct way:
string tomsZipCode = tom?.Address?.PostalCode?.ZipCode;
Notice the ?
before each property is being accessed? If any of those checks yield a null, subsequent checks are dropped and the null value is then assigned to the variable.
Closing Thoughts
Nullable Reference Types that Microsoft introduced in C# 8, provides the ability to be more explicit about the behaviors of your objects in C#. Prior to this feature, all custom objects – reference types – were implicitly nullable. Since this introduction, you can explicitly mark objects as nullable using the ?
character, much like how you mark value types as nullable. With this distinction, the compiler can now provide better development time warnings where there may be a possible dereference of a null value. We can revisit Nullable Reference Types separately in a future entry.