Exceptions of type NullReferenceException are the most common errors with .NET applications. With C# 8, nullable reference types have been introduced to get rid of these exceptions. The new .NET 6 and C# 10 project templates turn on nullable reference types. Over the times, many features in regard to nullability have been added to C#! This article shows many of these features.
Nullable Value Types
A value type cannot be null which made it difficult to map it with technologies that allow for optional values, such as the database or XML (nowadays JSON). One way to resolve this is by creating a reference type, and wrapping the value type within – as reference types can be null. C# 2 had another solution: nullable value types. The type Nullable
itself is a value type containing the type T, as well as a bool
value. The Boolean value is used with the HasValue
property, which returns true if the value is not null. Operators are overloaded to allow assigning null
.
The generic type Nullable
specifies the generic type T
with the constraint struct
, thus nullable value types can be used with structs.
Because Nullable
is a struct, the nullable value type does not have the overhead of a reference type – it can be put onto the stack, and the garbage collector is not responsible for the cleanup. The only additional overhead compared to a value type is the bool
variable.
Because this feature is often needed, C# 2 introduced syntax for value types. Instead of using Nullable x = null
, you can use int? x = null
.
More information on nullable value type in Chapter 2, "Core C#" of my book Professional C# and .NET – 2021 Edition.
Null-Conditional Operators
With C# 6, the null-conditional operators ?.
and ?[]
have been introduced. Only invoke the right expression if the left expression is not null. The next figure shows calling the Invoke
method of an event only if the event is not null. In the comments above this statement you can see the code that was necessary before this operator was available: first check if the handler is not null before calling the Invoke
method.
Using the null-conditional operator, you can also assign the result to a variable – the null-conditional operator returns null
if the expression left of this operator is null
.
Likewise, the ?[]
operator can be used to access arrays – null is returned if the array is null.
Null-Coalescing and Null-Coalescing Assignment Operators
The following code snippet shows the null-coalescing operator ??. With b = a ?? 10
, b
gets the value of a
assigned. Just if a
has a value of null
, then the value 10
is assigned to the variable b
. The property shown in the code snippet is of a reference type MyClass
. The variable _val
is declared to be nullable. With the get
accessor of the Val
property, the value of the variable _val
is returned. If _val
has a null value, a new object is created, assigned to the variable _val
, and finally returned from this property.
With C# 8, the null coalescing assignment operator was introduced. This is a combination of multiple operators, such as the +=
addition assigment operator combines the addition and the assignment, ??=
combines the null coalescing operator with the assginment. The code shown earlier can be simplified as shown in the next figure. If the variable _val has a null value, a new object is created, assigned to the variable, and returned from the property.
More information on C# operators including the null-conditional, null-coalescing and null-coalescing assingment operators in Chapter 5, "Operators and Casts" of my book Professional C# and .NET – 2021 Edition.
Nullable Reference Types (C# 8)
Nullable reference types have been introduced with C# 8. Enabling this feature, a reference type cannot have null assigned – unless the type is declared with a ?
. Because this feature is a breaking change with existing code, it needs to be enabled. Starting with .NET 6 and C# 10, new project templates turn by adding the “ element with the project file.
Enabling this feature adds annotations to method parameters and return values if null is allowed or not. With this, the compiler complains if null
is passed to parameters, and if a possible null
value is returned and used without checking. This helps to avoid exceptions of type NullReferenceException
. This feature doesn’t help to get rid of all the possible nullability issues, but helps getting rid of many of them.
The following feature shows the method ReturnANullableString
returning a nullable string that’s passed into the variable s
. Calling the ToUpper
method of the string
type can only be done before checking if this variable is not null – which is done first with checking for not null
, and then using the null coalescing operator.
Enabling this feature can be done with the enable
configuration in the project file, or using the #nullable
preprocessor directive to set a nullable context for a specific code block. With this preprocessor directive, you can enable
or disable
warnings no matter how the project file is configured, and restore
the configuration from the proejct file.
Migrating code to nullability, often some more changes than just adding the annotions for nullability are required. If there’s an issue that cannot be solved that easily, and you know the variable is never null, you can use the null forgiving operator !
. However, usually some light code changes help.
The compiler only accepts initialization of non-nullable reference types within the constructor. If a helper method called from the constructor, or a constructor from a base class does some initialization, you’ll get a compiler warning. One example how such issues can be solved is shown in the following code snippet creating an EF Core context.
Migrating the source code to enable nullable reference types introduced some issues which have been resolved with newer C# and .NET versions. For example, creating generics you had to decide for a generic type T to be either a nullable value or a nullable reference type. Later on other constraints have been added. With the constraint class
, the generic type needs to be a reference type. The constraint class?
allows the type to be a nullable or a non-nullable reference type. The constraint notnull
, the type can either be a struct or a class, but not nullable.
Some attributes have been added for the static analysis for the compiler. One example is NotNullIfNotNull
which specifies that the return value is not null if the argument where the attribute is used is not null. The following code snippet shows the user of NotNullWhen
with a TryParse
method: when the return type is true, the out value IPEndPoint
returned is not null. If the method returns false, IPEndPoint can be null. See a link for all the attributes available for null-state static analysis with a link below.
ArgumentNullException (.NET 6, C# 11)
Even if you use nullable reference types, an exception of type ArgumentNullException
should be thrown if a null value is passed to the method not expecting null. With the nullable annotation, the compiler generates a warning passing the wrong value. You can even change it to an error instead of a warning. However, not probably not all clients (if you create a library) have nullable reference types enabled, and thus it’s a good practice to throw exceptions if null is passed.
The next image shows the traditional way to throw exceptions of type ArgumentNullException
.
With .NET 6 you can reduce the number of code lines needed with the new static method ThrowIfNull
. ThrowIfNull has a second parameter that is optional, and by default – using the attribute CallerArgumentExpression
– takes the name of the parameter, similar to the implementation before with the nameof
expression:
A new extension with C# 11 is the bang-bang operator !!
. This operator just needs to be added to the parameter, and the implementation to throw an ArgumentNullException
is generated from the compiler:
You might think that the bang-bang operator is not necessary anymore having nullable reference types. Why should you check for
null
, if the compiler already complains ifnull
is passed as a parameter to a method ifnull
is not expected. You can also change the compiler setting to having errors instead of warnings. If you already migrated all your code to nullable reference types, you might get rid of some nullability checks. However, creating libraries you should keep adding checks. As nullable reference types are a breaking change, it will take many years until all the existing code is converted. Having a little!!
in your code increases developer productivity a lot instead of creating a few code lines that need to be written again and again.
Take away
Over the times, more and more features for nullability have been added to C# – starting with nullable value types, and the breaking change with nullable reference types. Some features are here to increase the code quality. As a NullReferenceException
is the most common exception with .NET applications, the code quality can be greatly improved. Other features are here to reduce the number of code lines to write, examples are the null-coalescing assignment operator and the bang-bang operator.
Enjoy learning and programming!
Christian
It would be great if you show support buying a coffee.

More Information
For more information on C# read my new book 🙂
Professional C# and .NET – 2021 Edition
See Chapters 1-13 for the foundations of C#, 14-23 .NET platform features, 24-28 ASP.NET Core, and 29-31 WinUI!
Related Information
Attributes for null-state static analysis interpreted by the C# compiler
Attributes for null-state static analysis interpreted by the C# compiler
Parameter Null Checking Specification
Image Classical Symphony Shines With Musical Notes From An Ancient Book ID 70230127 © Romolo Tavani | Dreamstime.com
NewCarInfo? handler = NewCarInfo; ->
EventHandler? handler = NewCarInfo;
in the first code snippet
LikeLiked by 1 person