Nullable Reference Types

In this article, we will go through the Nullable reference types feature of C# 8.
 
So far only value types had nullable types and starting C# 8, even reference types can be declared as nullable types.
 
Traditionally developers considered that reference type is meant to accept non-null and null both and there wasn't any explicit handling required and unfortunately this consideration is one of the primary root causes of famous "Null Reference Exception".
 
The primary reason for NullReferenceException is that reference in question doesn't have any valid memory address and hence while de-referencing (reading value from memory address) it doesn't know from where to read the value and hence fails.
 
Categorizing reference types into nullable and non-nullable helps to clear the intent during the design phase and now reference types are by default defined as non-nullable and to make them nullable, explicit null casting (i.e.?) is required.
 
Non-nullable reference types don't need to go through a null-check before dereferencing (i.e. reading data from memory) however it's required for nullable reference types
and compiler issues warning, in case the value is known to be null.
 

Nullable Context Overview

 
Nullable contexts are defined values for the compiler to interpret reference type variables. There are two types of Nullable contexts as follows.
  • The nullable annotation context: It can be enabled or disabled for any given source line. You can consider the pre-C# 8 compilers for the disabled annotation context.
  • The nullable warnings context: It may be set to enabled, disabled, or safeonly. The nullable warnings context specifies the warnings generated by the compiler using its flow analysis.
The nullable annotation and warning context can be set for a project using the Nullable element in your csproj file. These settings define how the compiler interprets the nullability of types and what warnings are generated.
 
Nullable context is set in csproj file as follows.
  1. <LangVersion>8.0</LangVersion>  
  2. <Nullable>enable</Nullable>  
The following table illustrates the configuration of the nullable annotation and warning context are based on Nullable attribute in csproj file.
 
Nullable element value
Nullable annotation context
Nullable warning context
enable
enabled
enabled
disable
disabled
disabled
safeonly
enabled
safeonly
warnings
disabled
enabled
safeonlywarnings
disabled
safeonly
 
Now, let's understand the meaning of these values in the context of annotation and warning.
 
When Nullable element value is set as enable, a nullable annotation context is enabled and nullable warning context is enabled. As a result, reference type variables (for example string) becomes non-nullable. All nullability warnings are enabled.
 
In case, Nullable element value is set as disable, both nullable annotation context and nullable warning context is disabled. It essentially means that you are working in the pre- C# 8 version. All nullability check and warnings are disabled.
 
If you set Nullable value as safeonly, you basically set a nullable annotation context as enabled and nullable warning context as safeonly. It essentially means that variables of a reference type are non-nullable and only safety nullability warnings are enabled.
 
By setting Nullable value as warnings, you end up setting nullable annotation context as disabled and nullable warning context as enabled. Due to this, variables of a reference type become oblivious (same as pre-C#8 version) however all nullability warnings are enabled.
 
Finally, when you set Nullable entry as safeonlywarnings, the nullable annotation context is set as disabled and nullable warning context is set as safeonly. It means that variables of a reference type are oblivious (same as pre-C#8 version) and all safety nullability warnings are enabled.
 
Apart from csproj file, these settings can also be configured at the source code and below is the list of code that can be used. You can use directives to set these same contexts anywhere in your project as follows.
  • #nullable enable: Sets the nullable annotation context and nullable warning context to enabled.
  • #nullable disable: Sets the nullable annotation context and nullable warning
context to disabled. It is the same as working in the pre-C# 8 version.
  • #nullable safeonly: Set the nullable annotation context to enabled, and the warning context to safeonly.
Apart from the above directives that work for both nullable annotation context and nullable warning context, there are some that can be used only for nullable warning context and the list goes as follows.
  • #pragma warning disable nullable: Set the nullable warning context to disabled.
  • #pragma warning enable nullable: Set the nullable warning context to enabled.
  • #pragma warning safeonly nullable: Sets the nullable warning context to safeonly.
The compiler uses the following rules when disabled nullable annotation context is selected
  • The behavior is the same as previous versions of C#.
  • The nullable references approach doesn't exist as all reference variables may be assigned to null.
  • No warnings are generated when a reference type variable is dereferenced. The null-forgiving operator (i.e. !) may not be used.
On the other side when the nullable annotation context is set as enabled, the compiler uses the following rules.
  • Any reference type variable is a non-nullable reference. Any non-nullable reference may be dereferenced safely.
  • Any nullable reference type may be null. If the value is known to be non-null after static analysis, the compiler warns you.
  • You can use the null-forgiving operator ! to declare that a nullable reference isn't null.
Note
Null-forgiving operator (!) is an anti-pattern and you should avoid using it. It only turns off the compiler-checks however at runtime, the value may still be null. It should only be used in a rare scenario when the compiler is not able to detect that a nullable value is actually non-nullable.
 

Using Nullable Context in Code

 
So far, we looked at the underlying concepts required for nullable reference types now let's have a look at the code and analyze how the warnings would be generated based on a nullable context setting.
  1. public static void ExecuteNonNullableReferenceType()  
  2. {  
  3.    #nullable enable  
  4.    string name = null;  
  5.    var myName = name.ToString();   
  6.    #nullable restore  
  7. }  
And here's goes the warnings from the above code.
 
Working With Nullable Reference Types In C# 8.0
 
Since nullable tag is set as enabled, you can see the warning however, above code would generate no warning if the nullable warning context is set as disabled or safeonly.
 
Now let's review another code snippet to understand the difference.
  1. public static void ExecuteNullableReferenceType()  
  2. {  
  3.    #nullable enable  
  4.    string? name = null;  
  5.    var myName = name.ToString();   
  6.    #nullable restore  
  7. }  
The above code generates only a single warning as the variable is declared as nullable so it can accept nullable.
 
Working With Nullable Reference Types In C# 8.0
 
The above code would generate a warning when the nullable context is safeonly or enabled and no warning in case of disabled.
 
Now let's make a small change in the above code and add a Null Forgiving Operator (!) and see the result.
  1. public static void ExecuteNullableReferenceType2()  
  2. {  
  3.    #nullable enable  
  4.    string? name = null;  
  5.    var myName = name!.ToString(); // Null Forgiving Operator   
  6.    #nullable restore  
  7. }  
The result goes as following,
 
Working With Nullable Reference Types In C# 8.0
 
There is no warning despite nullable context is enabled because compiler warnings have been overridden by the Null Forgiving Operator to say that variable is not having a null.
 
Note
The default nullable annotation and warning contexts are set as disabled. It means that your existing code compiles without changes and without generating any new warnings. However, if you want to apply intent- based design, the recommendation is to enable the nullable annotation and warning contexts.
 

Summary

 
In this article, we have gone through the background of nullable reference types and why it is required. Later we deep dived into nullable and warning annotation context and where these can be set. We have also seen the use of Null Forgiving Operator that alters the behavior of the compiler. Clearly nullable reference types enforces the intent based design and can help to reduce null reference exception at runtime.
Author
Prakash Tripathi
29 40.3k 6.3m
Next » Async Streams, Static Local Functions