14.3. Nullable Types and Null Operators#

null is the source of countless runtime crashes. Modern C# (8+) provides tools to make null-handling explicit, safe, and concise.

14.3.1. Nullable Value Types#

By default, value types (int, double, bool) cannot be null. Append ? to opt in:

int  x = null;   // compile error
int? y = null;   // OK

Check before use with .HasValue / .Value, or use the null operators below.

int? score = null;

if (score.HasValue)
    Console.WriteLine($"Score: {score.Value}");
else
    Console.WriteLine("No score yet.");

// Shorthand with null-coalescing
int display = score ?? 0;
Console.WriteLine($"Display: {display}");  // Display: 0

14.3.2. Nullable Reference Types (C# 8+)#

Enable with #nullable enable (or project-wide via <Nullable>enable</Nullable>). Once enabled, string means non-nullable; string? means may be null. The compiler warns if you dereference a potentially-null reference.

#nullable enable

string  name = "Alice";   // cannot be null
string? nick = null;      // may be null

// Console.WriteLine(nick.Length);  // warning: dereference of possibly null
Console.WriteLine(nick?.Length);  // OK — prints nothing if null

14.3.3. The Null Operators#

Operator

Name

Meaning

x ?? y

Null-coalescing

Return x if not null, else y

x ??= y

Null-coalescing assignment

Assign y to x only if x is null

x?.Member

Null-conditional

Access Member only if x is not null

x?[i]

Null-conditional index

Index only if x is not null

x!

Null-forgiving

Suppress the nullable warning (use sparingly)

// ?? — null-coalescing
string? input = null;
string result = input ?? "(none)";
Console.WriteLine(result);  // (none)

// ??= — assign only if null
input ??= "default";
Console.WriteLine(input);  // default

// ?. — null-conditional chain
string[]? words = null;
int? len = words?.Length;
Console.WriteLine(len);  // (nothing printed — len is null)

14.3.3.1. Chaining ?. and ??#

Combine them for safe, concise access with a fallback.

record Order(string? CustomerName, Address? ShippingAddress);
record Address(string City, string Zip);

Order? order = null;

// Safe deep access with fallback
string city = order?.ShippingAddress?.City ?? "Unknown";
Console.WriteLine(city);  // Unknown

14.3.4. Pattern Matching and Null#

Pattern matching integrates cleanly with nullability:

string? s = GetValue();

string result = s switch
{
    null              => "(none)",
    { Length: 0 }    => "(empty)",
    _                 => s
};

14.3.5. Practice#

  1. Write int SafeParse(string? input) that returns the parsed integer, or 0 if input is null or not a valid number. Use int.TryParse.

  2. Given List<string?> names, use LINQ and ?? to produce a List<string> where every null is replaced with "Anonymous".

  3. Rewrite this safely using null operators:

    if (user != null && user.Address != null)
        Console.WriteLine(user.Address.City);
    

Footnotes