Nullable Reference Types in C#



Nullable reference types in C# are a set of features that were introduced in C# 8.0 and help to minimise the chances of the runtime throwing a System.NullReferenceException.

Nullable reference types have three main features that help avoid these exceptions, including marking a reference type as nullable −

  • Improved flow analysis − C# can now check if a variable might be null before we use it.
  • API annotations − Special attributes help C# understand if a value from an API can be null or not.
  • Variable annotations − Developers can mark variables to show whether they can be null.

What Are Nullable Reference Types?

A nullable reference type in C# does not change how our code runs; instead, it enhances compile-time checking. The nullable reference type helps the compiler track which variables are nullable (can hold null) and which are non-nullable (should never hold null).

Nullable reference types help developers write safer and more reliable code by letting the compiler warn us when a variable might be null.

Now let's learn how the three features work to produce warnings when your code might dereference a null value. Dereferencing a variable means to access one of its members using the .(dot) operator, as shown in the following example

string message = "Hello, World!";
// dereferencing "message"
int length = message.Length;

When we dereference a variable whose value is null, the runtime throws a System.NullReferenceException.

Similarly, warnings can be produced when [] notation is used to access a member of an object when the object is null −

using System;
public class Collection<T> {
   private T[] array = new T[100];
   public T this[int index] {
       get => array[index];
       set => array[index] = value;
   }
}
public class myProgram{
   public static void Main(){
      Collection<int> c = default;
	  // CS8602: Possible dereference of null
      c[10] = 1;
   } 
}

Declaring Nullable and Non-Nullable Types

We can declare once the nullable reference types are enabled −

  • Non-nullable reference types cannot hold null values.
  • Nullable reference types can hold null, and are marked with a ? symbol.
string name = "Aman";    // Non-nullable
string? nickname = null; // Nullable

If we try to assign null to name, the compiler will show a warning −

string name = null;      // Warning: Possible null assignment

Preventing Null Reference Warnings (Dereferencing)

Warnings are produced when we dereference a variable whose value is null, which causes the runtime to throw a System.NullReferenceException. Dereferencing means accessing one of its members using the '.' (dot) operator or '[]' notation.

string message = "Hello, World!";
// Dereferencing "message" (safe, as it's non-nullable)
int length = message.Length; 

C# compiler specifically warns when we try to dereference a nullable reference type before checking its value:

using System;
public class myProgram {
   public static void Main() {
      // The result might be null
      string? message = GetMessage();
      // Warning (CS8602): Possible dereference of a null reference.
      // int length = message.Length; 
      
      // Correct Way: Use Improved Flow Analysis
      if (message != null){
         // No warning here
         int safeLength = message.Length;
      }
   } 
   static string? GetMessage() => new Random().Next(2) == 1 ? "Hello" :null;
}

The Null-Forgiving Operator (!)

The null-forgiving operator (or "dammit operator"), denoted by an exclamation mark (!), is a tool to suppress a compiler warning. It is a runtime assertion; we are telling the compiler, "Trust me, we guarantee this value is not null here."

The '!' operator does not change the runtime value. If the variable is actually null, a NullReferenceException will still occur when dereferenced.
string? nullableMessage = GetMessage();

// Using '!' to force the compiler to treat it as non-null
int length = nullableMessage!.Length; // Compiler warning suppressed

Enabling Nullable Reference Types

The feature is opt-in and requires explicit enabling in our project. We must be using C# 8.0 or later.

Project-Level Enabling (Recommended)

Add the following tag within a '<PropertyGroup>' element in your '.csproj' file. This is the most consistent approach.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <Nullable>enable</Nullable> 
  </PropertyGroup>
</Project>

File-Level Directives

For large codebases or gradual migration, we can control the setting on a file-by-file basis −

#nullable enable  // Turns on NRT warnings for the rest of the file
// ... your code here ...
#nullable disable // Turns off NRT warnings for subsequent code

Don’t confuse Nullable Reference Types (like string? in C# 8.0) with Nullable Value Types (like int? in C# 2.0). Although both use the ? symbol, they work differently. Nullable Value Types allow value types (such as int, bool, etc.) to hold null, while Nullable Reference Types help you avoid null reference errors by tracking where null might appear in your code.

Feature Nullable Reference Type ('string?') Nullable Value Type ('int?')
Introduced C# 8.0 C# 2.0
Mechanism Compile-time annotation/static analysis. Uses the System.Nullable<T> struct at runtime.
Purpose Prevent runtime NullReferenceException. Allows a value type (which cannot normally be null) to represent missing data.

Conclusion

Nullable Reference Types make our code safer and more reliable. They help us find potential null issues during compilation instead of at runtime. We can reduce the chances of getting a System.NullReferenceException by marking the reference types as nullable or non-nullable and by using compiler warnings effectively.

Advertisements