Isaac.

C# Nullable Reference Types

Eliminate null reference exceptions with nullable reference types.

By EMEPublished: February 20, 2025
csharpnullablenull safetytypes

A Simple Analogy

Nullable reference types are like seatbelts. They alert you when something might be null, helping you catch potential crashes before they happen.


Why Nullable Reference Types?

  • Null safety: Compiler prevents null reference exceptions
  • Documentation: Code shows intent (nullable or not)
  • Reduced bugs: Catch issues at compile-time
  • Backward compatible: Opt-in per project
  • Better design: Forces thinking about null cases

Enable Feature

// In .csproj
<PropertyGroup>
    <Nullable>enable</Nullable>  // Or "warnings", "annotations"
</PropertyGroup>

// Or in file
#nullable enable

public class Order
{
    public string Id { get; set; } = "";  // Non-nullable, must initialize
    public string? Description { get; set; }  // Nullable, can be null
}

Non-Nullable by Default

#nullable enable

public class User
{
    // Non-nullable properties
    public string Name { get; set; } = "";  // Must initialize
    public int Age { get; set; }
    public string Email { get; set; } = "";

    // Nullable properties  
    public string? Phone { get; set; }  // Can be null
    public string? Address { get; set; }
    
    // Constructor
    public User(string name, string email)
    {
        Name = name;
        Email = email;
        Age = 0;
        // Phone and Address can be null
    }
}

// Compiler prevents this
var user = new User(name: null, email: "test@example.com");  // ERROR

// And this
string notnull = user.Phone;  // ERROR: Phone can be null

// But allows this
string? nullable = user.Phone;  // OK

Null Coalescing

#nullable enable

public class OrderService
{
    public void ProcessOrder(Order? order)
    {
        // Option 1: Check for null
        if (order == null) return;
        
        var total = order.Total;  // Safe after null check
        
        // Option 2: Coalesce
        var description = order.Description ?? "No description";
        
        // Option 3: Conditional access
        var customerName = order.Customer?.Name ?? "Unknown";
        
        // Option 4: Null forgiving operator (use sparingly!)
        var id = order.Id!;  // I know it's not null
    }
}

Validation with Null Checks

public class UserValidator
{
    public bool Validate(User? user)
    {
        if (user == null)
            return false;
        
        if (string.IsNullOrEmpty(user.Name))
            return false;
        
        if (user.Age < 0 || user.Age > 150)
            return false;
        
        return true;
    }
}

// Usage
User? user = GetUserOrNull();

if (user != null && user.Name.Length > 3)
{
    // Safe: both user and Name are non-null here
    ProcessUser(user);
}

Nullable Collections

public class Order
{
    // Non-null list, non-null items
    public List<OrderItem> Items { get; set; } = new();
    
    // Nullable list (list itself can be null)
    public List<OrderItem>? OptionalItems { get; set; }
    
    // Non-null list with nullable items
    public List<string?> Tags { get; set; } = new();
}

// Usage
var order = new Order();

// Safe
order.Items.Add(new OrderItem());  // Items is never null

// Need to check
if (order.OptionalItems != null)
{
    order.OptionalItems.Add(new OrderItem());
}

// Can contain nulls
order.Tags.Add(null);  // OK
string? tag = order.Tags[0];  // Could be null

Best Practices

  1. Enable globally: Set in project file
  2. Use #nullable enable: In legacy codebases
  3. Suppress warnings: Only when justified
  4. Null forgiving sparingly: Use ! rarely
  5. Document nullable: Explain why null is possible

Related Concepts

  • Option types (functional programming)
  • Maybe monad
  • Null object pattern
  • Defensive copying

Summary

Nullable reference types bring compile-time null safety to C#. Enable them project-wide to catch null reference exceptions before runtime.