The “Billion Dollar Mistake”—null content—has plagued C# developers since version 1.0. NullReferenceException is the most common runtime error. C# 8.0 introduces Nullable Reference Types (NRT), a feature that moves null checking from runtime to compile time. This is not just a syntax change; it’s a fundamental shift in how we write safety-critical C# code.
How NRT Works
Traditionally, reference types (string, class) were nullable by default. In C# 8.0 with NRT enabled:
stringis non-nullable.string?is nullable.
<PropertyGroup>
<Nullable>enable</Nullable>
<WarningsAsErrors>CS8600;CS8602;CS8603</WarningsAsErrors>
</PropertyGroup>
The Static Analysis Engine
The compiler now tracks the “null-state” of variables flow-sensitively.
public void ProcessUser(User? user)
{
// user is "maybe-null" here
// Console.WriteLine(user.Name); // ❌ Compiler Warning: CS8602
if (user != null)
{
// user is "not-null" here (Smart Cast)
Console.WriteLine(user.Name); // ✅ Safe
}
}
Advanced Annotations
Sometimes the compiler’s static analysis isn’t smart enough, especially with helper methods or generics. We use attributes from System.Diagnostics.CodeAnalysis.
[NotNullWhen]
public static bool TryGetConfig(string key, [NotNullWhen(true)] out string? value)
{
if (_config.ContainsKey(key))
{
value = _config[key]; // value is not null here
return true;
}
value = null;
return false;
}
// Usage
if (TryGetConfig("db", out string? connection))
{
// Compiler knows 'connection' is not null because result was true
Connect(connection);
}
[AllowNull] and [DisallowNull]
Useful for properties where get/set have different nullability contract.
public class Cache
{
[AllowNull] // Setter accepts null (to clear cache)
[return: MaybeNull] // Getter might return null
public string LastValue
{
get => _cached ?? null;
set => _cached = value ?? "";
}
}
Migration Strategy for Large Codebases
Turning on <Nullable>enable</Nullable> on a million-line legacy codebase will generate 10,000 warnings. Don’t do it all at once.
- Step 1: Enable for new projects only.
- Step 2: Use
#nullable enabledirectives at the top of actively edited files. - Step 3: Use the “Nullable” property in
Directory.Build.propsto toggle per-project.
Entity Framework Core Pitfalls
EF Core requires special handling because properties are initialized via reflection, not constructors.
public class Customer
{
public int Id { get; set; }
// ❌ Warning: Non-nullable string must contain value
// public string Name { get; set; }
// ✅ Solution 1: Default!
public string Name { get; set; } = default!;
// ✅ Solution 2: Constructor binding
public Customer(string name) => Name = name;
}
Key Takeaways
- NRT isn’t just metadata; it’s robust static analysis.
- Use attributes like
[NotNullWhen]to help the compiler. - Adopt incrementally using
#nullable enable. - Be careful with serialization and EF Core; use
default!(the null-forgiving operator) responsibly.
Discover more from C4: Container, Code, Cloud & Context
Subscribe to get the latest posts sent to your email.