Introduction
Softwares can fail at two different moments: compile time and runtime. In the context of errors, null is a problem because the effects of not handling them right are perceived only at runtime.
In this post, I’ll show how to use a C# language feature to move the null errors to compile time and help us avoid them at runtime.
The problem with null
Null is a value that is not a value. It is used by reference types to indicate that the variable is not pointing to any value.
This creates a special case that needs to be checked every time the variable is accessed or it will throw an exception at runtime when the value is null.
This code, for example, will compile normally:
|
|
But will throw a NullReferenceException at runtime:
Unhandled exception. System.NullReferenceException:
Object reference not set to an instance of an object.
The solution is to check for null
before accessing any reference type property or method:
|
|
The problem is remembering to check for null every time an object can be null. That’s where Nullable reference types comes to the rescue.
Nullable Reference Types
Reference types, as the name implies, store references (pointers) to their data.
C# provides the following reference types:
- string
- object
- class
- record
- interface
- delegate
- dynamic
All those types can have null
assigned to them, making them a risk for a NullReferenceException
.
C# 8 introduced Nullable Reference Types. It is a way to say that variables may contain null and get warnings every time their members are accessed without checking for nulls.
To declare nullable reference types, we use ?
after the type name:
string? stringThatMayBeNull = null; //Nullable string
Person? personThatMayBeNull = null; //Nullable Person
List<Person>? personThatMayBeNull = null; //Nullable List of Non-Nullable Person
List<Person?> personThatMayBeNull = new(){ null }; //Non-Nullable List of Nullable Person
To enable nullable reference types, just add the following property to your projects:
<Nullable>enable</Nullable>
Nullable warnings
When we enable nullable reference types in our projects, we start to get warnings when assigning null
to non-nullable types:
We also start to receive warnings when accessing a nullable type member when it can be null:
The compiler knows when a variable isn’t null.
For instance, when we check for null in an if statement:
And when we assign a non-nullable value to a nullable variable:
Treating nulls
Besides checking for nulls with if
statements, we can use null operators that C# provides to make the code cleaner.
Null conditional operator
The null conditional operator (?.
) allows us to access a reference type member only if its value is not null.
|
|
Null forgiving operator
The null forgiving operator (!.
) informs the compiler that we are sure that a nullable type variable that may be null is not null.
This is commonly useful when writing automated tests because you know the results that will be returned.
|
|
Null coalescing operator
The coalescing operator (??
) is used to assign the value of the right operand if the value of the left operand is null. It’s used to assign a nullable reference type to a non-nullable reference type, falling back to a default value in case of null.
|
|
Enforcing null checks at build time
To enforce null checks everywhere, we can increase the severity of the warnings to Error
. This way, it won’t be possible to compile the projects without properly checking for nulls.
Unfortunately, compiler messages for nullables are not in code quality and code style categories explained in my previous post and can’t be set to errors unless done by message code:
|
|
To do it, set the property TreatWarningsAsErrors
to Nullable
on all projects:
<TreatWarningsAsErrors>Nullable</TreatWarningsAsErrors>
After this, all warnings from nullability messages will have their severity equal to Error
:
💡 We can set
TreatWarningsAsErrors
totrue
to treat all warnings as errors.
Required properties
With nullable types enabled, we will receive the following errors on classes/records that have non-nullable reference types not initialized in the constructor or with default values:
C# 11 introduced the required
modifier. It allows us to have non-nullable reference types without initializing them in the constructor by forcing us to specify the values for these properties when instantiating an object.
First we add the required
modifier to the properties:
Then, when instantiating the object, we need to pass the values for the required properties:
If we don’t specify the values, the compiler will warn us:
Automated Tests
It’s worth noting that using the null conditional and the null coalescing operator will influence the code coverage of the project. They will be treated as branches and have to have tests for both scenarios.
For example, if we have this silly class:
|
|
And this test:
|
|
The code coverage will be only 50% because the test is not covering the scenario receiving null.
Creating a new test for the null scenario:
|
|
Code coverage will be 100% for this class: