With nullable types in C# programs, every value type, like int
, can be null
. We wrap an int
inside a nullable struct
to have a null
int
.
Nullable types are constructed by specifying the question mark after a value type in a declarator statement. The nullable int
is specified with the syntax "int
?"
Int
exampleA nullable variable is specified with a "?" character. We can use a question mark (at the end of a value type) to transform the type into a nullable type.
null
int
type, the HasValue
and Value properties. We use the nullable type to acquire the value of the instance.int
type is aliased to System.Int32
—using "System.Int32
?" would work as well.HasValue
property returns a bool
that indicates whether the nullable instance contains a set value.HasValue
property is true, you can access the Value property without an exception.using System; // // Create a local variable of type nullable integer. // ... It is initially assigned to null. // ... The HasValue property is false. // int? value = null; Console.WriteLine(value.HasValue); // // Assign the nullable integer to a constant integer. // ... The HasValue property is now true. // ... You can access the Value property as well. // value = 1; Console.WriteLine(value.HasValue); Console.WriteLine(value.Value); Console.WriteLine(value); if (value == 1) { Console.WriteLine("True"); }False True 1 1 True
To use a nullable bool
, use the type "bool
?" with the trailing question mark. This is a struct
that contains a bool
. The "bool
?" can be set to null
, true and false.
bool
?" occupies 2 bytes in memory. It has an extra byte
of overhead beyond a regular bool
.null
, true and false value in a single variable.using System; class Program { static void Main() { bool? tristate = null; tristate = true; tristate = false; Console.WriteLine(tristate); long m1 = GC.GetTotalMemory(false); bool?[] b1 = new bool?[100000]; long m2 = GC.GetTotalMemory(false); b1[0] = false; Console.WriteLine("{0} bytes per bool?", (m2 - m1) / 100000); } }False 2 bytes per bool?
enum
We consider a tri-state enum
, which can be implemented with a byte
backing store. Notice how the semicolon syntax ": byte
" is used after the enum
type declaration.
enum
can be set to Tristate.Null
, Tristate.True
and Tristate.False
. It works like any other enum
.bool
, all 3 values can be represented in one byte
of storage.enum
type avoids the overhead associated with wrapping a value type in a generic struct
.using System; class Program { enum Tristate : byte { Null = 0, True = 1, False = 2 } static void Main() { Tristate tristate = Tristate.Null; tristate = Tristate.True; tristate = Tristate.False; Console.WriteLine(tristate); long m1 = GC.GetTotalMemory(false); Tristate[] b1 = new Tristate[100000]; long m2 = GC.GetTotalMemory(false); b1[0] = Tristate.False; Console.WriteLine("{0} byte(s) per Tristate", (m2 - m1) / 100000); } }False 1 byte(s) per Tristate
DateTime
exampleDateTime
is a struct
, so it cannot directly be null
, but we can have a nullable DateTime
wrapper. We use the question mark syntax, which results in the type "DateTime
?"
DateTime
?" is a struct
that wraps a DateTime
struct
, providing another level of indirection that can simplify some programs.DateTime
?" variable is declared. It is passed as a parameter to the Test method, and is assigned to different values.GetValueOrDefault
method will return DateTime.MinValue
for a null
DateTime
.using System; class Program { static void Main() { // // Declare a nullable DateTime instance and assign to null. // ... Change the DateTime and use the Test method. // DateTime? value = null; Test(value); value = DateTime.Now; Test(value); value = DateTime.Now.AddDays(1); Test(value); // // You can use the GetValueOrDefault method on nulls. // value = null; Console.WriteLine(value.GetValueOrDefault()); } static void Test(DateTime? value) { // // This method uses the HasValue property. // ... If there is no value, the number zero is written. // if (value.HasValue) { Console.WriteLine(value.Value); } else { Console.WriteLine(0); } } }0 9/29/2009 9:56:21 AM 9/30/2009 9:56:21 AM 1/1/0001 12:00:00 AM
struct
We can directly access the Nullable generic struct
. Here we use a Nullable int
by specifying the Nullable type. This syntax has the same effect, but is more verbose.
using System; class Program { static void Main() { // Use Nullable directly. Nullable<int> test = 100; if (test.HasValue) { Console.WriteLine("HAS VALUE: {0}", test.Value); } // Set Nullable int to null. test = null; if (test.HasValue) { Console.WriteLine("NOT REACHED"); } } }HAS VALUE: 100
This program calculates the memory usage for allocating a large array of nullable integers. You can declare a nullable type array using int
?[] as the type.
GC.GetTotalMemory
method is used to determine the resource usage of the program before and after the allocation occurs.using System; class Program { static void Main() { // // Compute the memory usage for a nullable type integer. // ... The program allocates one million nullable int structs. // const int size = 1000000; long b1 = GC.GetTotalMemory(true); int?[] array1 = new int?[size]; long b2 = GC.GetTotalMemory(true); array1[0] = null; Console.WriteLine((b2 - b1) / (double)size); } }8.000016
With "null
," we can indicate an int
is invalid, missing or uninitialized. No special values (like -1) are needed. This can make code more reliable.
When you use a nullable type, the C# compiler actually uses the Nullable T struct
. The T refers to the value type you are using (such as int
).
Nullable types are value types that are wrapped inside the nullable type. They can be useful when you want to add another state (invalid or uninitialized) to a value type.