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 example. A 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.
Here We see a null int type, the HasValue and Value properties. We use the nullable type to acquire the value of the instance.
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
Bool example. 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.
Info This syntax enables us to represent a 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?
Tri-state 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.
Info The enum can be set to Tristate.Null, Tristate.True and Tristate.False. It works like any other enum.
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 example. DateTime 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?".
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
Nullable 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
Memory usage. 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.
Result The nullable type wrapper requires 4 bytes of storage. And the integer itself requires 4 bytes for each element.
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
Notes, nullables. 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.
Implementation. 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).
Note Structs are allocated in continuous memory, and this makes nullable types fairly efficient.
Note 2 There is overhead to using nullable types—raw values are slightly faster.
A summary. 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.
Dot Net Perls is a collection of tested code examples. Pages are continually updated to stay current, with code correctness a top priority.
Sam Allen is passionate about computer languages. In the past, his work has been recommended by Apple and Microsoft and he has studied computers at a selective university in the United States.
This page was last updated on Apr 11, 2023 (simplify).