iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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 (enable) and the language stops letting you forget the null check.

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.

Example

Example
#nullable enable
string? name = MaybeName();
int len = name?.Length ?? 0;
Try it Yourself »

Discussion

Loading…