TL;DR
  • Enable C# nullable reference types for compile-time null-safety and fewer runtime exceptions.
  • Use nullable reference types in public APIs, new code, and data models; disable in legacy or third-party code if warnings overwhelm.
  • Mark nullable types with ? and always check for null before use.
  • Use the null-forgiving ! operator rarely and only when you’re certain a value isn’t null.
  • nullable reference types clarify intent, improve maintainability, and make APIs safer for consumers.

C# nullable reference types help catch null reference exceptions at compile time instead of runtime. Introduced in C# 8, they add null-safety annotations to your code, making the compiler warn you about potential null dereferences before they become production bugs.

Enabling nullable reference types in Your Project

Enable nullable reference types at the project level in your .csproj:

<PropertyGroup>
  <Nullable>enable</Nullable>
</PropertyGroup>

For granular control, use #nullable directives:

#nullable enable
public string? GetUserName(int userId)
{
    var user = _userRepository.Find(userId);
    return user?.Name; // Compiler knows this might return null
}
#nullable disable

Essential nullable reference types Syntax with Nullable Objects

Mark nullable types with ? and handle nullable class objects properly:

public class User
{
    public string Name { get; set; } = string.Empty;
    public string? Email { get; set; } // Can be null
}

public class UserService
{
    public User? FindUser(int id) // Method can return null
    {
        return _repository.GetById(id); // Might return null
    }
    
    public void ProcessUser(User? user)
    {
        if (user is null) return;
        
        Console.WriteLine(user.Name); // Safe - null check above
        Console.WriteLine(user.Email?.ToUpper()); // Safe - conditional access
    }
    
    public void ProcessLegacyData(User data)
    {
        // Only use ! when you're absolutely certain
        var result = GetUserFromCache()!.Process(data);
    }
}

When to Use nullable reference types

nullable reference types shine in these scenarios:

  • Public APIs - Clear contracts about what can be null
  • Data layer - Entity properties that map to nullable database columns
  • Domain models - Optional relationships and properties
  • New projects - Built-in null safety from day one

When to Disable nullable reference types

Turn off nullable reference types when they create more noise than value:

  • Legacy codebases - Massive warning floods during migration
  • Third-party integrations - Libraries without nullable reference type support
  • Generated code - Auto-generated classes that don’t need null checks
FeatureUse CaseCaution
User?Optional objects, search resultsCheck for null before use
string?API parameters, optional fieldsDon’t overuse - be intentional
#nullable enableNew modules, clean architectureCan overwhelm legacy projects
! operatorConfident null assertionsBypasses safety - use rarely

Key Takeaway

Treat nullable reference types as guardrails, not handcuffs. They’re most valuable in public APIs and new code where null-safety improves maintainability. For legacy systems or third-party integrations, selective disabling keeps your focus on code that actually benefits from null-safety annotations.

Start with nullable reference types enabled in new projects, then disable them strategically where the warnings don’t add value to your development workflow.

FAQ

What are nullable reference types in C#?

Nullable reference types let you annotate reference types as nullable or non-nullable. The compiler warns you about possible null dereferences, helping prevent runtime null reference exceptions.

How do you enable nullable reference types in a C# project?

Add <Nullable>enable</Nullable> to your .csproj file or use #nullable enable in your code. This tells the compiler to enforce null-safety checks and issue warnings for unsafe usage.

When should you use nullable reference types?

Use nullable reference types in public APIs, new projects, domain models, and data layers where nullability is part of the contract. They clarify intent and help catch bugs early.

When is it better to disable nullable reference types?

Disable nullable reference types in legacy codebases with many warnings, third-party integrations without NRT support, or auto-generated code where null-safety adds little value.

What does the ! operator do with nullable reference types?

The ! operator is a null-forgiving operator. It tells the compiler you are certain a value is not null, suppressing warnings. Use it sparingly, as it bypasses null-safety checks.

How do you handle nullable properties in classes?

Mark properties as nullable with ? (e.g., public string? Email { get; set; }). Always check for null before using these properties to avoid exceptions.

What are the main benefits of using nullable reference types?

Nullable reference types reduce null reference exceptions, clarify code contracts, and improve maintainability. They make APIs safer and help teams catch bugs at compile time.

What are common pitfalls when migrating to nullable reference types?

Migrating legacy code can produce many warnings. Address them incrementally, disable nullable reference types in noisy areas, and avoid overusing the null-forgiving operator.

How do nullable reference types affect API design?

Nullable reference types make API contracts explicit about which parameters and return values can be null. This improves documentation and helps consumers use your API correctly.

Can you use nullable reference types selectively in a project?

Yes, use #nullable enable and #nullable disable to control NRT enforcement in specific files or regions, allowing gradual adoption.
See other c-sharp posts