Nullable types were introduced with version 7.0 of C#. These types represent instances of the System.Nullable<T>
class. They are the values of an underlying type T
and an additional null
value. The T
can be any non-nullable value type, but it cannot be a reference type. This guide will introduce you to this relatively new feature and show you what kinds of problems it intends to solve. We will see practical examples that show how this new feature can fix most of your null-related bugs before they crash your application.
In 1965 a man named Tony Hoare, one of the giants of computer science, invented the null reference. At that time, he was designing the first type system for references in an object-oriented language called ALGOLW. His goal was to ensure the use of all references would be perfectly safe, and this was ensured by checks from the compiler. This is where he put the null reference, and the reason was simple: it was easy to implement. This led to countless errors, vulnerabilities ,and system crashes. It is assumed to have caused billions of dollars of damage in the last fifty or so years.
This is why the null reference has been called "the billion-dollar mistake."
The problem is how easy it is to use null references. These are the default value in C# for every reference type. The dilemma is answered with these questions:
The problem—or at least part of it—comes from the fact that C# does not let you express whether a null as a value in a specific context is good or bad.
What can you do?
We are using nullable types when we need to represent an undefined value of an underlying type. While Boolean values can have either true
or false
values, a null in this case means false
as there is no undefined value. When you have a database interaction, a variable value can be either undefined or missing. A field in a database may contain true
, false
, or no value at all. In this case, a nullable of type bool
is the way to go.
Some things to keep in mind as you work with nullable types:
null
value.T?
is the shorthand syntax for Nullable< T >
.int? = null
or double? = 10.0
.HasValue
and Value
readonly properties can be used to check and get the value from such a type.==
and !=
operators can be used with a nullable type.T?
is an instance where HasValue
evaluates to false.??
operator—null-coalescing operator—allows you to assign a value to the underlying type based on the value of the nullable type. Let's declare some nullable types.
1using System;
2
3namespace nllables
4{
5 class Program
6 {
7 static void Main(string[] args)
8 {
9 double? nullableDouble = 9.99;
10 int? nullableInt = null;
11 double standardDouble = 19.19;
12 Console.WriteLine($"The variable :: {nameof(nullableDouble)} has value :: {nullableDouble} and HasValue evaluates to :: {nullableDouble.HasValue}.");
13 if(nullableInt.HasValue)
14 { Console.WriteLine($"The variable :: {nameof(nullableInt)} has value :: {nullableInt.Value} and HasValue evaluates to :: {nullableInt.HasValue}"); }
15 else
16 { Console.WriteLine($"The variable :: {nameof(nullableInt)} has no value!"); }
17 Console.WriteLine($"The variable :: {nameof(standardDouble)} has value :: {standardDouble}.");
18 Console.ReadKey();
19 }
20 }
21}
This gives us the following output:
1The variable :: nullableDouble has value :: 9.99 and HasValue evaluates to :: True.
2The variable :: nullableInt has no value!
3The variable :: standardDouble has value :: 19.19.
In this demo, we'll use shorthand syntax to declare two nullable types, one double
and one int
. We use the HasValue
in the if
block to decide if the value for the int
is right or not.
If we try to refer to the int
without the if
block, we see the following exception:
1System.InvalidOperationException: 'Nullable object must have a value.'
This exception pops up at compile time. Let's fix the issue before it hits production.
Let's convert a nullable type to an underlying type.
1using System;
2
3namespace nllables
4{
5 class Program
6 {
7 static void Main(string[] args)
8 {
9 double? d = null;
10 int? c = 9;
11
12 int g = c ?? 99;
13 double f = d ?? 99.9;
14
15 Console.WriteLine("The nullables...");
16 if (d.HasValue)
17 { Console.WriteLine($"The variable: {nameof(d)} has value of {d.Value}"); }
18 else
19 { Console.WriteLine($"The variable: {nameof(d)} has is null!"); }
20
21 if (c.HasValue)
22 { Console.WriteLine($"The variable: {nameof(c)} has value of {c.Value}"); }
23 else
24 { Console.WriteLine($"The variable: {nameof(c)} has is null!"); }
25
26 Console.WriteLine("The converted...");
27 Console.WriteLine($"The variable: {nameof(g)} has value of {g}");
28 Console.WriteLine($"The variable: {nameof(f)} has value of {f}");
29
30 Console.ReadKey();
31 }
32 }
33}
The output is as follows:
1The nullables...
2The variable: d has is null!
3The variable: c has value of 9
4The converted...
5The variable: g has value of 9
6The variable: f has value of 99.9
We have two nullable type variables, d
and c
. The null-coalescing operator allows us to convert the variables as far as their values are concerned to standard variable types g
and f
.
Our conversion may need some explanation. The int g = c ?? 99;
line works behind the scenes as follows. It checks if the c
is a nullable type and calls the HasValue
operator If the return value is true
it is assigned to g
; otherwise 99** is assigned.
The same thing happens in
double f = d ?? 99.9;which shows another case, where the value is
nullfor the variable so
99.9is assigned as the value to the
f` variable. Keep in mind that these values need to be implicitly convertible as the value is concerned.
As a simple example shows,the following action will die with an exception:
1int g = c ?? 99.9;
The error states:
1CS0266 Cannot implicitly convert type 'double' to 'int'.
2An explicit conversion exists (are you missing a cast?)
What about the operators?
1using System;
2
3namespace nllables
4{
5 class Program
6 {
7 static void Main(string[] args)
8 {
9 double? a = null;
10 double? b = 99.9;
11
12 bool? c = true;
13 bool? d = false;
14 bool? e = null;
15
16 int? f = 10;
17 int? g = null;
18
19 Console.WriteLine("Do the math...");
20 Console.WriteLine($"a + b = {(a + b)}");
21 Console.WriteLine($"a * b = {a * b}");
22 Console.WriteLine($"a * f = {a * f}");
23 Console.WriteLine($"b * f = {b * f}");
24 Console.WriteLine("The booleans...");
25 Console.WriteLine($"c | d = {c | d}");
26 Console.WriteLine($"c | e = {c | e}");
27 Console.WriteLine($"d & e = {d & e}");
28 Console.WriteLine($"c & e = {c | e}");
29
30 Console.ReadKey();
31 }
32 }
33}
The result:,
1c | d = True
2c | e = True
3d & e = False
4c & e = True
From this experiment, we conclude that any operation in the field of mathematics produces nothing when it is involved with a null
on either side. As for the Boolean part, the situation is a bit more complicated because the c & e = True
shows that true & null
results in True
while false & null
evaluates to False
. This article further discusses nullable Boolean logical operators if you are interested.
This guide took us on a journey to the realm of nullable types. After a brief introduction and history you learned how to wield this new feature of C#. I hope this has beed informative to you and that you have found what you were looking for. If you liked this guide give it a thumbs up!