Skip to main content
CodeOath
← All posts

C#64 min total · 17 parts

C# Fundamentals: Value Types, Reference Types, Boxing, and the Type System

Part 12 of 17 · ~2 min

Nullable Value Types

By default, a value type has no way to represent "nothing" — try int quantity = null; and the compiler rejects it outright. Some SKUs genuinely have no stock count yet, though — a new product added to the catalog before anyone has physically counted it — and "zero" is the wrong way to represent that, because zero is also a perfectly valid real count. Nullable<T>, written T?, is the tool for exactly this distinction:

DateTime? lastCountedAt = null;   // never counted, as opposed to counted-and-we-forgot-when

if (lastCountedAt.HasValue)
    Console.WriteLine($"last counted {lastCountedAt.Value}");
else
    Console.WriteLine("never counted");

// null-coalescing: fall back to a sentinel default cleanly
DateTime effectiveDate = lastCountedAt ?? DateTime.MinValue;

Underneath the ? syntax sits Nullable<DateTime>, an ordinary struct built from just two pieces — a copy of the DateTime itself, and a flag recording whether that copy should be trusted at all. Skip the check and touch .Value while that flag says no, and the runtime throws — verified directly rather than assumed: "Nullable object must have a value." Think of it as the value-type cousin of a null-reference crash. Reading a nullable count before confirming it's actually set is an easy habit to fall into, and it costs you exactly as dearly as skipping a null check anywhere else in the codebase.

Keep this separate from nullable reference types — writing Shipment? with <Nullable>enable</Nullable> switched on for the project. That feature lives entirely at compile time: it lets static analysis warn you when a Shipment? might be getting dereferenced without a null check first, and it stops existing the moment the build finishes. It changes no runtime behavior at all for a reference type — a Shipment? holding an actual null sits there completely inert, and nothing goes wrong until some later line tries to call a method on it.