C# Nullable Features thru the times

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.

Musical Notes

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.

Musical Notes

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.

Null-Conditional Operator

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.

Null Coalescing Operator

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.

Null Coalescing Assignment Operator

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.

Nullable Reference Types

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.

EF Core Context intializing a DbSet Property

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.

Not Null When

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.

Throw 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:

Throw ArgumentNullException with .NET 6

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:

Throw ArgumentNullException with C# 11

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 if null is passed as a parameter to a method if null 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.

Buy Me 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!

Trainings

Related Information

Nullable Value Types

Attributes for null-state static analysis interpreted by the C# compiler

C# preprocessor directives

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

4 thoughts on “C# Nullable Features thru the times

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.