Checking Null and Undefined objects
.NET .NET Core Best Practices C# Visual Studio

How to use Null Reference Types in .NET 8

Welcome to today’s post.

In today’s post, I will be discussing what null reference types are in C# and .NET applications. I will also be showing how to use null reference types in some useful scenarios during application development.

The feature of nullable reference types has been part of the Visual Studio development environment since the release of C# version 8.0.

The configuration of nullable reference types is also used in other development languages such as Typescript used in the Angular frontend development framework.

One of the reasons for introducing it is to ensure that null checking of values is enforced when null values are assigned to a property or method that are not explicitly declared as nullable (with the ? operator). This reduces the occurrences of null reference exceptions during runtime and allows them to be handled better.

I will explain how this has evolved in the first section.

Inclusion of the Default Project Option for Nullable Reference Types

More recently, the inclusion of nullable reference types has not been included as the default project option until Visual Studio 2022.

Before Visual Studio 2022, and before the release of the .NET 8 framework, projects that were created with the built-in templates by default did not include a default to the null reference types.

The project properties interface shows the Nullable context option available within the Build | General section:

The available values that can be selected for the nullable context are shown below:

In previous versions (VS2017/VS2019), the project file <PropertyGroup> tags would contain elements like this:

<PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <UserSecretsId>…</UserSecretsId>
</PropertyGroup>

In more recent versions, the project file <PropertyGroup> tags would contain elements like this:

<PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

The main difference is that in the older versions, the default for the nullable reference type was to disable it. This meant that the nullable flag was set to disable.

The way this change impacted developers is explained in the next section.

Impact of Setting Nullable Reference Types as a Project Default

By making the nullable reference types as a default, this has the impact of breaking existing projects that make extensive use of classes that declare properties and members.

It is not just during the compilation of the project that you will notice warnings on properties, member fields, and method/function parameters, but also when your application is run!

The reason is that when nullable reference types are disabled, the property and member fields of a class are permitted to have null values assigned to them by default during runtime. What this means is that the property can have a null or non-null value assigned to it, and the application will run fine without any potential run-time errors.

On the other hand, when nullable reference types are enabled, the property and member fields of a class are not permitted to have null values assigned to them by default. Then this means is that the property can only have a non-null value assigned to it. In this situation, the application can run into potentially unexpected null-exception run-time errors.

The above scenarios are especially apparent when there are many layers of libraries and classes that contain properties that are written to be by default, supporting nullable values.

Given the above explanation, I will go through one example on how the default nullable option impacts an application that is used to retrieve and display data.

Example of an Application Breaking with Default Nullable Reference Types

A situation where I had an ASP.NET Core MVC application I had written in a version of .NET 3.1 and C# 8 a while back, and progressively migrated it to newer versions of .NET and C#. With each migration I built and ran the application successfully. When I reached versions .NET 8.0 and C# 12, the application after being re-built and run, suddenly resulted in the application breaking with a run-time null-exception error.

Confused with what was going on, I ran the debugger and found that there was an exception like this:

Microsoft.EntityFrameworkCore.Query: Error: An exception occurred while iterating over the results of a query for context type 'BookLoanWebAppWCAG.Data.ApplicationDbContext'.
System.Data.SqlTypes.SqlNullValueException: Data is Null. This method or property cannot be called on Null values.
   at Microsoft.Data.SqlClient.SqlBuffer.ThrowIfNull()
   at Microsoft.Data.SqlClient.SqlBuffer.get_String()
   at Microsoft.Data.SqlClient.SqlDataReader.GetString(Int32 i)
…

Thinking I had worked out the issue, I inspected the model class I had used to store the retrieved SQL data. The class is shown below:

using System;
using System.ComponentModel.DataAnnotations;

namespace BookLoanWebAppWCAG.Models
{
    public class BookViewModel
    {
        public int ID { get; set; }
        public string Title { get; set; }
        public string Author { get; set; }
        public int YearPublished { get; set; }
        public string Genre { get; set; }
        public string Edition { get; set; }
        public string ISBN { get; set; }
        public string Location { get; set; }
        public string MediaType { get; set; }
        public DateTime DateCreated { get; set; }
        public DateTime DateUpdated { get; set; }
        public BookViewModel() { }
    }
}

We can see that, at some point during the record iteration by the data reader, a null string value was read, and an attempted cast was applied to convert it to an integer. This resulted in the application falling over.

To remediate the issue, I applied the following changes within the model class:

public class BookViewModel
{
    public int? ID { get; set; }
    public string? Title { get; set; }
    public string? Author { get; set; }
    public int? YearPublished { get; set; }
    public string? Genre { get; set; }
    public string? Edition { get; set; }
    public string? ISBN { get; set; }
    public string? Location { get; set; }
    public string? MediaType { get; set; }
    public DateTime? DateCreated { get; set; }
    public DateTime? DateUpdated { get; set; }
    public BookViewModel() { }
}

The above change to the class properties to make them nullable types with the suffix operator “?” resolves the issue, but does it really solve my problem? No. The reason is that with some classes, I may wish to enforce not-null constraints on some properties that are perhaps used in user interfaces, or with data that is retrieved from a data store that already has data field constraints based on the underlying data schema.

If we reinstated the changes to our model class properties and applied the following change in the project file:

<PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>disable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

Or change the project properties as shown:

Then we would be back to our previous application behaviour of treating all reference types as nullable and lower the likelihood of getting the null exception errors.

There is another way in which we can determine which parts of our application use nullable reference types or not. I will show how this is done in the next section.

Selectively Enabling Nullable Reference Types within an Application

If the idea of disabling or enabling null reference types for the entire application may cause undesirable side effects and run time errors as the application is being modified extended, and re-tested, then we can make use of a useful compiler directive. This directive is #nullable

Instead of remediating code for class properties within our application, we can use the nullable directive as shown to disable nullable reference types in the current source file:

#nullable disable

using System;
using System.ComponentModel.DataAnnotations;

namespace BookLoanWebAppWCAG.Models
{
    public class BookViewModel
    {
        public int ID { get; set; }
        public string Title { get; set; }
        …
        …

In the final section, I will provide some suggestions on applying null reference types within .NET projects.

Advice on Applying Null Reference Types within Projects

My advice for using null reference types is with small projects, you can start by enabling null reference types at the project level, then make changes to existing source as you build the application and run tests to ensure no runtime errors. When null reference types are enabled, applying the “?” suffix operator to reference types will allow that type to be nullable.

With larger projects, you can start by disabling null reference types at the project level, then selectively use nullable directive to apply nullable reference types on a file level. This way, you minimize the impact of the breaking change. When null reference types are disabled, applying the “?” suffix operator to reference types will produce a compiler warning, but will not make that type nullable.

We have seen how to apply the null reference types to a project within Visual Studio and to selective files within a project.

We have also seen how nullable reference types can break the functionality of an existing project. 

We have also learnt how to decide on when to use nullable reference types depending on the size and complexity of our project.  

That is all for today’s post.

I hope that you have found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial