Skip to content

C# Nullable Reference Types — Complete Guide

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about C# Nullable Reference Types. We cover key concepts, practical examples, and best practices.

You shipped a feature that crashes with NullReferenceException every time a customer hits the edge case you forgot to guard against. Your team spends two hours debugging a null that the compiler could have caught. C# 8.0 introduced nullable reference types so the compiler can track null-state for you.

Wrong

public class OrderService
{
    public string GetCustomerEmail(Order order)
    {
        return order.Customer.Email.ToLower();
    }
}

Output: NullReferenceException: Object reference not set to an instance of an object.

The compiler stays silent even though order, order.Customer, or order.Customer.Email could be null.

#nullable enable
public class OrderService
{
    public string GetCustomerEmail(Order? order)
    {
        return order?.Customer?.Email?.ToLower() ?? "no-email@example.com";
    }
}

Output: "no-email@example.com" when any part of the chain is null — no crash.

The ? after the type name marks it as nullable. The ?. null-conditional operator short-circuits if the left side is null. The ?? null-coalescing operator provides a default.

Prevention

  • Enable nullable in your .csproj with <Nullable>enable</Nullable>.
  • Annotate every parameter, return type, and field as T? or T.
  • Treat nullable warnings as errors: <WarningsAsErrors>Nullable</WarningsAsErrors>.
  • Use ArgumentNullException.ThrowIfNull(obj) for non-nullable guard clauses.
  • Use the null-forgiving operator ! only when you are absolutely certain the value is not null.
  • Use nullable annotations on public API surfaces for library consumers.
  • Enable nullable in new projects by default and migrate existing projects file by file using #nullable enable.

Common Mistakes with nullable reference

  1. Using return to exit a function early instead of wrapping a pure value in the monad
  2. Mixing let bindings with <- bindings in do notation, producing type errors
  3. Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors

These mistakes appear frequently in real-world CSHARP code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

What does #nullable enable do?

It activates the nullable annotation context and nullable warning context for that source file. The compiler tracks which reference types can be null and warns when you dereference without checking.

Do I need to annotate every reference type?

Yes, every reference type parameter, return type, and field should be annotated as nullable (T?) or non-nullable (T). Unannotated types generate a warning (level 2 by default) so there is no ambiguity.

How do I suppress a false-positive nullable warning?

Use the null-forgiving operator ! on the expression to tell the compiler "I know this is not null." For larger regions, use #nullable disable as a last resort. The goal is zero false positives without disabling the feature entirely.

This pattern is used in Doda Browser's extension pipeline to guarantee that message payloads are never null before dispatch. For more C# best practices, visit DodaTech.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro