Introduction
The designers of C#2 have added the concept of nullable types to deal with a weakness of value types versus reference types. It is then essential to have properly assimilated these two notions.
Value types and null value paradigm
A reference is null when it does not reference any object. This is the default value taken by all references. You just need to have a glance at the code of any application to notice that developers commonly take advantage of null references. In general, the use of a null reference allows communicating some information:
- A method which must return a reference to an object returns a null reference to indicate that the requested object cannot be found. This relieves developers from having to implement a binary error code.
- When you encounter a method which accepts an argument of a reference type which can be null, this means that this argument is generally optional.
- A field of a reference type which is null can be used to indicate that the object is being initialized, updated or even deleted and that it does not have a valid state.
The notion of nullness is also commonly used within relational databases to indicate that a value in a record is not assigned. This notion can also be used to designate an optional attribute in an XML element.
Amongst the several differences between the value and reference types, we can take a look at the fact that the notion of nullness doesn't exist for value type. This generally causes several problems. For example, how to interpret a null integer value (or not yet assigned) retrieved from a database or from an XML document? Multiple solutions exist but none are fully satisfying:
- If the whole range of integer values is not usable, we create a convention. For example, a null integer value is represented by an integer equal to 0 or 1. The several disadvantages to this approach are evident: constraint to maintain everywhere in the code, possibility of changes in the range of values taken by this field...
- We create a wrapper structure containing two fields, an integer and a boolean, that is set to false, means that we have a null value. Here, we must manage an additional structure for value type and an additional state for each value.
- We create a wrapper class containing an integer field. Here, the disadvantage is that in addition to having to maintain a new class, we add a significant burden to the garbage collector by creating several small objects on the heap.
- We use boxing, for example by casting our integer value into a reference of type object. In addition to not being type-safe, this solution also has the disadvantage of overloading the garbage collector.
To deal with this recurring problem, the designers of C#2/.NET2 decided to provide the concept of nullable types.
The System.Nullable<T> structure
The 2005 .NET framework offers the System.Nullable<T> generic structure which is defined as follows:
public struct System.Nullable<T>
{
public Nullable(T value);
public static explicit operator T( T? value );
public static implicit operator T?( T value );
public bool HasValue { get; }
public T Value { get; }
public override bool Equals( object other );
public override int GetHashCode();
public T GetValueOrDefault();
public T GetValueOrDefault( T defaultValue );
public override string ToString();
}
This structure responds well to the problem of null values when the T type parameter takes the form of a value type such as int. Here is a little example which illustrates the use of this structure. The first version of Fct() uses the nullness of the string type while the second version uses the nullness of an instance of the Nullable<int> structure:
class Foo
{
static string Fct( string s )
{
if ( s == null )
return null;
return s + s;
}
static System.Nullable<int> Fct( System.Nullable<int> ni )
{
if ( !ni.HasValue )
return ni;
return (System.Nullable<int>) ( ni.Value + ni.Value );
}
}
Evolution of the C# syntax: Nullable<T> and the null keyword
The C# syntax allows you to assign and to compare null keyword to an instance of System.Nullable<T>:
Nullable<int> ni = null;
System.Diagnostics.Debug.Assert(ni == null);
These two lines are equivalent to:
// Call the default ctor which internally set 'Hasvalue' to false.
Nullable<int> ni = new Nullable<int>();
System.Diagnostics.Debug.Assert(!nullable1.HasValue);
The use of the System.Nullable<T> structure is intuitive but can quickly become blurring. It is not obvious that these two programs are equivalent (the two pieces of generated IL code are equivalent):
class Program
{
static void Main()
{
System.Nullable<int> ni1 = 3;
System.Nullable<int> ni2 = 3;
bool b = ( ni1 == ni2 );
System.Diagnostics.Debug.Assert( b );
System.Nullable<int> ni3 = ni1 + ni2;
ni1++;
}
}
using System;
class Program
{
static void Main()
{
Nullable<int> ni1 = new Nullable<int>(3);
Nullable<int> ni2 = new Nullable<int>(3);
bool b = ( ni1.GetValueOrDefault() == ni2.GetValueOrDefault() ) && ( ni1.HasValue == ni2.HasValue );
System.Diagnostics.Debug.Assert( b );
Nullable<int> ni3 = new Nullable<int>();
if ( ni1.HasValue && ni2.HasValue )
ni3 = new Nullable<int>( ni1.GetValueOrDefault() + ni2.GetValueOrDefault () );
if ( ni1.HasValue )
ni1 = new Nullable<int>( ni1.GetValueOrDefault() + 1 );
}
}
Also it might seem strange that the ni++ instruction called on the ni variable which is supposed to be null does not cause an exception of type NullReferenceException.
Evolution of the C# syntax: equivalence between Nullable<T> and T?
In C#2, you can follow the name of a non-nullable value type T by a question mark. In this case, the C#2 compiler will replace all T? expressions by Nullable<T>. To simplify, you can imagine that this is a pre-processing which is done directly on the source code, a little like a pre-compiler. Hence, the following line...
int? i = null;
... is equivalent to:
Nullable<int> i = null;
For example, the two following methods are rigorously equivalent:
class Foo
{
static System.Nullable<int> Fct1( System.Nullable<int> ni )
{
if ( !ni.HasValue )
return ni;
return (System.Nullable<int>) ( ni.Value + ni.Value );
}
static int? Fct2( int? ni )
{
if (ni == null)
return ni;
return ni + ni;
}
}

Join the conversation! Your thoughts help the community grow.