Nullable Reference Types
Nullable reference types make T and T? distinct in the compiler: T must hold a value, T? may be null. The compiler tracks flow and warns when youre about to dereference something it cannot prove is non-null. Turn it on per-project (
NRT annotations, flow analysis, and operators
EXAMPLE
#nullable enable
using System;
using System.Collections.Generic;
class NullableDemo
{
// 1) Parameter types declare intent
static int LengthOf(string s) => s.Length; // s cannot be null
static int LengthOf(string? s) => s?.Length ?? 0; // s may be null
// 2) Properties — declare each field as nullable or not
record User(string Email, string? DisplayName);
static void Main()
{
var alice = new User("alice@example.com", null);
Console.WriteLine(alice.Email.Length); // ok
// Console.WriteLine(alice.DisplayName.Length); // WARNING: possible null
// 3) Null-conditional ?. and null-coalescing ??
Console.WriteLine(alice.DisplayName?.ToUpper() ?? "(no name)");
alice = alice with { DisplayName = "Alice" }; // record with-expression
Console.WriteLine(alice.DisplayName!.Length); // ! = "I am sure" (rarely needed)
// 4) Flow analysis — after a null check, the compiler narrows the type
if (alice.DisplayName is not null)
{
Console.WriteLine(alice.DisplayName.Length); // no warning here
}
// 5) Patterns that establish non-null
switch (alice.DisplayName)
{
case string s when s.Length > 0: Console.WriteLine($"hi {s}"); break;
default: Console.WriteLine("no name"); break;
}
// 6) Helper attributes nudge the compiler when it cannot infer
if (TryGetUser("u1", out var u))
{
Console.WriteLine(u.Email); // compiler knows u is non-null here
}
// 7) Collections of nullable values
var emails = new List<string?> { "a@x", null, "b@x" };
var nonNull = emails.Where(e => e is not null).Select(e => e!.ToLowerInvariant());
// 8) Dictionary lookup — old, surprising default vs the safer pattern
var prefs = new Dictionary<string, string> { ["theme"] = "dark" };
// var v = prefs["accent"]; // throws KeyNotFoundException
if (prefs.TryGetValue("accent", out var accent))
{
Console.WriteLine(accent);
}
}
// [NotNullWhen(true)] tells the compiler: when this method returns true,
// the out parameter is non-null. Call sites get smart narrowing for free.
static bool TryGetUser(string id, [System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out User? u)
{
if (id is null or "") { u = null; return false; }
u = new User($"{id}@example.com", "user-" + id);
return true;
}
}
Why it matters
Treat the null-forgiving operator `!` like an unsafe cast. Every `!` in your code is a place where the compiler thinks something might be null and you have told it to stop checking. Audit them periodically; refactor to a real null check or a nullability attribute so the compiler regains the proof.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Discussion
Loading…