C# Nullable Types

Null Basics
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, Value Type
// 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


Nullable<T> struct
T? translates into System.Nullable<T>. Nullable<T> is a light-weight immutable struct, having only two fields to represent Value and HasValue. The essence of System.Nullable<T> is very simple:

public struct Nullable<T> where T : struct
{
public T Value {get;}
public bool HasValue {get;}
public T GetValueOrDefault();
public T GetValueOrDefault(T defaultValue);
...
}

The code:

int? i = null;
Console.WriteLine (i == null); // true

gets translated by the compiler to:

Nullable<int> i = new Nullable<int>();
Console.WriteLine (! i.HasValue); // true

Attempting to retrieve Value when HasValue is false throws an InvalidOperationException. GetValueOrDefault() returns Value if HasValue is true; otherwise, it returns new T() or a specified a custom default value.

The default value of T? is null.


Implicit and explicit nullable conversions
The conversion from T to T? is implicit, and from T? to T is explicit. For example:

int? x = 5; // implicit
int y = (int)x; // explicit

The explicit cast is directly equivalent to calling the nullable object's Value property. Hence, if HasValue is false, an InvalidOperationException is thrown.


Boxing and unboxing nullable values
When T? is boxed, the boxed value on the heap contains T, not T?. This optimization is possible because a boxed value is a reference type that can already express null.



Lifted Operators
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? 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.


Equality operators (== !=)
Lifted equality operators handle nulls just like reference types do. This means two null values are equal:

Console.Write ( null == null); // True
Console.Write ((bool?)null == (bool?)null); // True


Further:
If exactly one operand is null, the operands are unequal.
If both operands are nonnull, their Values are compared.


Relational operators (< <= >= >)
The relational operators work on the principle that it is meaningless to compare null operands. This means comparing a null value to either a null or nonnull value returns false.

bool b = x < y; // Translation:

bool b = (x == null || y == null)
? false
: (x.Value < y.Value);


All other operators (+ -* / % & | ^ << >> + ++ --! ~)
These operators return null when any operands are null. (This pattern should be familiar to SQL users.)

int? c = x + y; // Translation:
int? c = (x == null || y == null)
? null
: (int?) (x.Value + y.Value);


Mixing nullable and nonnullable operators
You can mix and match nullable and nonnullable types (this works because there is an implicit conversion from T to T?):

int? x = null;
int y = 2;
int? z = x + y; // equivalent to x + (int?)y


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

If the unknown value was false, the result would be true.
If the unknown value was true, the result would be true.

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

bool? n = null;
bool? f = false;
bool? 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)


Null Coalescing Operator
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

The ?? operator is equivalent to calling GetValueOrDefault with an explicit default value.

0 comments


Subscribe to Developer Techno ?
Enter your email address:

Delivered by FeedBurner