Nullable Types

Reference types can represent a nonexistent value with a null reference. Value types, however, cannot ordinarily represent null values. For example:

string s = null;   // OK - reference type.
int i = null;      // Compile error - int cannot be null.

To represent null in a value type, you must use a special construct called a nullable type. A nullable type is denoted with a value type followed by the ? symbol:

int? i = null;                     // OK - Nullable Type
Console.WriteLine (i == null);     // True

The Nullable<T> struct does not define operators such as <, >, or even ==. Despite this, the following code compiles and executes correctly:

int? x = 5;
int? y = 10;
bool b = x < y;      // true

This works because the compiler steals or “lifts” the less-than operator from the underlying value type. Semantically, it translates the preceding comparison expression into this:

bool b = (x.HasValue && y.HasValue)
          ? (x.Value < y.Value)
          : false;

In other words, if both x and y have values, it compares via int’s less-than operator; otherwise, it returns false.

Operator lifting means you can implicitly use T’s operators on T?. You can define operators for T? in order to provide special-purpose null behavior, but in the vast majority of cases, it’s best to rely on the compiler automatically applying systematic nullable logic for you.

The compiler performs null logic differently depending on the category of operator.

When supplied operands of type bool?, the & and | operators treat null as an unknown value. So, null | true is true, because:

Similarly, null & false is false. This behavior would be familiar to SQL users. The following example enumerates other combinations:

bool? n = null, f = false, t = true;
Console.WriteLine (n | n);    // (null)
Console.WriteLine (n | f);    // (null)
Console.WriteLine (n | t);    // True
Console.WriteLine (n & n);    // (null)
Console.WriteLine (n & f);    // False
Console.WriteLine (n & t);    // (null)

The ?? operator is the null coalescing operator, and it can be used with both nullable types and reference types. It says “If the operand is nonnull, give it to me; otherwise, give me a default value.” For example:

int? x = null;
int y = x ?? 5;        // y is 5

int? a = null, b = 1, c = 2;
Console.Write (a ?? b ?? c);  // 1 (first nonnull value)

The ?? operator is equivalent to calling GetValueOrDefault with an explicit default value, except that the expression passed to GetValueOrDefault is never evaluated if the variable is not null.