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
- Enable globally: Set in project file
- Use #nullable enable: In legacy codebases
- Suppress warnings: Only when justified
- Null forgiving sparingly: Use
!rarely - 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.