C# Nulls – Part 2 – Nullable Reference Types

What Are Nullable Reference Types?

In Part 1 I dove into various improvements with the C# language in relation to null handling and new operators. In this part, I am going to discuss Nullable Reference Types. The direction in how C# has evolved has provided better tools and constructs to assist with the creation of safer code.  In my view, a major improvement was the introduction of Nullable Reference Types was introduced in C# 8.0.

By default, reference types are nullable and have been for a long time in many languages. I covered a small bit of history as to why this is the case in Part 1.

After enabling Nullable Reference Types in C# this is turned on its head. Reference types become [expected to be] non-nullable unless you explicitly state when null is an allowed value. When enabled the compiler (and code hints from within Visual Studio Code) will inform you of code that is failing to handle a null condition.

Let’s begin with an example. Here is a very simple program that has a function that returns a ClientRecord. This is out of the box C# with default project options before we enable Nullable Reference Types.

Function GetClientRecord returns a reference type. When Nullable Reference Types are disabled, this function’s contract, as defined by the function’s signature, is to return an instance of a ClientRecord class or null. The ability to return null is implied – which could easily be overlooked by us fallible humans.

The calling code in Main() is not checking for a null value. In this example, a null value is not returned so its not a worry. And perhaps it was not a worry for the developer who first coded this.

Then Someone Comes Along and Makes A Change

Imagine if GetClientRecord was in another file or API, perhaps maintained by someone else, is in use elsewhere and at some point in the future it was changed to:

The function’s contract has not changed. Its signature is the same. It is the code within that has changed. However, when running our example program it now fails with an Object reference not set to an instance of an object exception.

The following is a screenshot from within Visual Studio Code. Remember we currently have Nullable Reference Types is disabled which is the default for C# projects. There are no warnings or issues reported with this code.

Screenshot of main function showing no indication of any issues.
Screenshot of function Main, showing no indication of any issues.

Can this be improved?

Would it not be great if the C# compiler and Visual Studio [Code] could warn us that we have code that is not handling nulls?

The good news is that we can. But we will need to indicate in our code when using reference types which ones can be nullable and which ones should not. Otherwise, the compiler will give us warnings when not handling nulls when there is no need to do so. We would then be forced to ignore the warnings or unnecessarily code for null conditions that will never happen.

Enabling Null Reference Types

To enable Null References Types, you can do this in one of two ways. For the entire project, you can add a <Nullable> directive to your project’s .csproj file.

Or alternatively, you can add a #nullable enable compiler directive to the top of your .cs file. An example of this is shown below.

I advise that you should be doing both on all new projects and when working on existing code add the #nullable directive. For existing code, you may find that too many changes are required at once and one change leads to another.

With Nullable Reference Types Enabled

Let’s go back to the original version of our program but with Nullable Reference Types enabled.

This works fine with no issues. We are not making any use of null.

Let’s say, for our function GetClientRecord we wish to return a record when the ID is valid and return null when not.

The program will still compile but a warning will be issued “warning CS8603: Possible null reference return“. Within Visual Studio Code the warning is highlighted like this:

Screenshot of C# warning of possible null reference return.
Screenshot of C# warning of possible null reference return.

So What’s Going On Here?

As mentioned above when Nullable Reference Types are disabled (the default language behaviour) the function’s contract is to return either an instance of ClientRecord or null.

With Nullable Reference Types enabled, the contract is changed, in that without it being expressively indicated, its contract is to only return an instance of ClientRecord.

If the compiler were to strictly enforce our new contract it should refuse to compile. Instead, it issues warnings to indicate that the function’s signature indicates that it will only return a ClientRecord but the code itself does not adhere to that contract.

You could argue that in an ideal world the compiler should refuse to compile due to the broken contract. This contract could be the cause of a bug. Some languages, like Rust, will refuse to compile if the code and contract do not match. It actively prevents the development of code with undefined behaviour. However, to do so in C# now, would break legacy code. By making Nullable Reference Types optional, off by default and the issuing of warning rather than errors, we have a half-way house of allowing development to continue and transition over time.

In part 3 of this series, I will continue with the example code within this article showing how to resolve the compiler warning we have introduced and make our code more null safe.

Leave a Reply

Your email address will not be published. Required fields are marked *