Nullable value type is a type that allows you to represent not only all values of its underlying type, but also the null value.
Why do we need nullable value types? For example, an int type variable can have values ranging from -2,147,483,648 to 2,147,483,647. Some cases require specification that a variable value is not defined or missing. For example, a column value in a database row. Nullable value types have been created for such cases. These types are instances of the System.Nullable<T> structure.
You can define an int variable that allows null as follows:
Nullable<int> nullableInt;
However, a shortened entry is more common:
int? nullableInt;
For the above C# declarations, the same IL code will be generated.
Both values of the underlying type and null are written to a variable via simple assignment:
int? nullableIntLhs = 62;
int? nullableIntRhs = null;
The HasValue property allows you to find out whether a variable of nullable value type contains a value of the underlying type:
int? iNullable = 62;
int result;
if (iNullable.HasValue)
result = iNullable.Value;
else
result = -1;
// result == 62
In addition to calling the HasValue property, you can check for a value by comparing to null. The following checks are equal, the same IL code generates for them:
int? nullableInt = null;
bool hasValue1 = nullableInt.HasValue;
bool hasValue2 = nullableInt != null;
The Value property returns the value of the underlying type if there is one (Nullable<T>.HasValue - true). Otherwise, we'll get the InvalidoperationException:
int? iNullable = ....;
int result;
if (iNullable.HasValue)
result = iNullable.Value; // OK
else
result = iNullable.Value; // InvalidOperationException
The T GetValueOrDefault() method is basically similar to Value. The difference is that T GetValueOrDefault() doesn't throw an exception, but returns a default value of T type if there is no value of the underlying type:
int? iNullable = ....;
int result;
if (!iNullable.HasValue)
result = iNullable.GetValueOrDefault(); // result == 0
The T GetValueOrDefault (T defaultValue) method is similar to the Value property. The only difference is that it does not generate an exception. It returns the value of the DefaultValue argument if there is no value of the underlying type:
int? iNullable = ....;
int result;
if (!iNullable.HasValue)
result = iNullable.GetValueOrDefault(62); // result == 62
For Nullable<T>, there are defined operators: implicit conversion from T to Nullable<T> and explicit conversion from Nullable<T> to T.
You can assign T values to Nullable<T> variables directly:
Nullable<int> nullableInt;
nullableInt = 62;
To write a value from Nullable<T> to a T variable, you will need to perform explicit casting. If the underlying value is missing (Nullable<T>.HasValue - false) in Nullable<T>, we'll get InvalidOperationException when performing explicit casting.
Example:
Nullable<int> nullableIntLhs = 62;
int resultLhs = (int)nullableIntLhs; // OK, 62
Nullable<int> nullableIntRhs = null;
int resultRhs = (int)nullableIntRhs; // InvalidOperationException
This may be confusing given what you've read above. Besides, the following code is successfully compiling:
Nullable<int> nullableInt = null;
However, you should remember that Nullable<int> is a value type. Hence null here is just syntax sugar. In this case, the nullableInt variable will be initialized with the default(Nullable<int>) value.
All the variables below will have the same value:
Nullable<int> nInt1 = null;
Nullable<int> nInt2 = new Nullable<int>();
Nullable<int> nInt3 = default(Nullable<int>);
int? nInt4 = null;
int? nInt5 = new int?();
int? nInt6 = default(int?);
It becomes more obvious if you look at IL code, where the same value is explicitly used to initialize all variables:
IL_0001: ldloca.s nInt1
IL_0003: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0009: ldloca.s nInt2
IL_000b: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0011: ldloca.s nInt3
IL_0013: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0019: ldloca.s nInt4
IL_001b: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0021: ldloca.s nInt5
IL_0023: initobj valuetype [mscorlib]System.Nullable`1<int32>
IL_0029: ldloca.s nInt6
IL_002b: initobj valuetype [mscorlib]System.Nullable`1<int32>
Boxing of Nullable<T> values has a number of specifications:
If unary and binary operators (for example, '+', '-') are supported by T, then the following rule applies to Nullable<T>:
Results table:
For greater/less comparison operators ('<', '<=', '>', '>='):
Results table:
Equality operator ('=='):
Results table:
Inequality operator ('!='):
Results table:
Operator '&':
Results table:
Operator '|':
Results table:
0