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;
class Program
{
static void Main()
{
//
// 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.
Opinion This is an efficient implementation. In an array many nullable types are stored in contiguous memory.
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).
Structs Structs are allocated in continuous memory, and this makes nullable types fairly efficient.
However 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 Aug 22, 2021 (image).