Nullable<T>
and T?
For as long as I can remember, people have been asking why they can't set an int
variable to null, or why they can't return null from a method declared to return DateTime
.
Many who understand why they couldn't do so still wished they could, particularly when working
with databases.
.NET 2.0 provides the generic struct System.Nullable<T>
with the constraint that T
must be a value type. (If you know absolutely nothing about generics, now might
be a good time to learn about the basics before reading further. You don't need to know a lot of the details
however, and the basic concept is a lot simpler than full-blown generics sometimes gets, which is why I've
put this page before the one on generics.) Nullable<T>
itself is still a value type, but
it represents the same set of values as T
plus the "null" value. It maintains a separate
member in memory, which is exposed through the HasValue
property. When this is true, the
Value
property represents the overall value. When it's false, the overall value is null.
C# provides language support for nullable types using a question mark as a suffix. For example,
int?
is the same type as Nullable<int>
(which is also the same
type as Nullable<System.Int32>
in the normal way). C# then allows you to compare
a nullable value with null, or set it to null, and these work in the obvious way. There's an implicit
conversion (no cast required) from a non-nullable type to its equivalent nullable type, and there's
an explicit conversion (cast requried) from a nullable type to its equivalent non-nullable type.
The cast is compiled into a call to the Value
property, and an InvalidOperationException
is thrown if the value is null at that point. A nullable type can also be used as the right hand side of
the as
operator, with the natural consequences.
The boxed type of a nullable value is the boxed type of the equivalent non-nullable value. If you box a value
which is already null (i.e. HasValue
is false), the result is null. This was a late change
to the behaviour, as it required CLR changes which Microsoft were hoping to avoid - you may therefore see
some beta documentation which disagrees with this.
Note that unlike in SQL, two null values of the same type are equal. In other words, the following:
int? x = null; int? y = null; Console.WriteLine (x==y); |
prints "True"
.
As well as the System.Nullable<T>
struct, there's the non-generic
static class System.Nullable
. This merely provides
support for the System.Nullable<T>
struct, in terms of finding out the non-nullable
type of a nullable type and performing comparisons.
bool?
has various binary logic operators, but not all of the ones available on bool
.
Importantly, the "shortcut" operators (&&
and ||
) aren't defined for
bool?
. A null value represents a sort of "don't know" value - so for instance, null | true
is true
, but null | false
is null
; similarly null & false
is false
but null & true
is null
.
This is a really simple little operator which I suspect will come in quite handy -
if it's widely known about. (It was a long time before I saw anything about it.)
Basically, a ?? b
is similar to a==null ? b : a
. The type of
a
has to be a nullable type or a reference type, and the type of b
has to be a suitable type, the details of which are best left to the spec.
The result is the value of a
if that's non-null, otherwise it takes the value of
b
. a
is only evaluated once (contrary to the version presented
above using the conditional operator), and b
is only evaluated
at all if a
evaluates to null.
The operator is right-associative, so a ?? b ?? c
is equivalent to
a ?? (b ?? c)
- in other words, if you provide a string of
a1 ?? a2 ?? ... ?? an
then the result is the first non-null one (or null if
all of them are null).
One nice feature is that if the type of a
is a nullable type and the type of
b
is the equivalent "non-nullable" type, the result of the expression is
that non-nullable type. For example, you can do:
// GetSomeValueMaybe() is a method returning an int? value int? possible = GetSomeValueMaybe(); int definite = possible ?? 5; // Default to 5 // Alternatively, just: int value = GetSomeValueMaybe() ?? 5; |
This is possible because the compiler knows that if the first expression evaluates to null it
will use the second expression. In this case, we're effectively using GetSomeValueMaybe()
with a kind of "default value" of 5.
The usual details about conversions and the precise rules which are applied can be found in the spec.
Back to the main C# page.