Nullable Types in C# - A Beginner's Tutorial
Rahul Singh
Principal Engineering Manager | Solution Architect | Microsoft MVP | CodeProject MVP | Author | Udemy Instructor
Introduction
This article talks about Nullable types in C#. We will see when could we find ourselves in need for Nullable types and what should we know if we are dealing with Nullable types.
Background
The null value is very useful in C#. For any type the null could be used to identify whether this particular variable contains a value or not. Typical use of null is to assign a variable null value to indicate that this variable has not been initialized. Whenever we need use this, we first check whether it is null or not, if it is null then perhaps we need to initialize and use it and if it is not then we can use it right away.?
Now if we consider the same scenario for built in numeric types or struct types, then How will we represent the absence of value. In an int type all the values are valid int values. The moment I declare int, it contains some default value. So is it possible to use null assignment and checking with int(or struct types)??
The answer to the above question is - No, it is not possible to assign/check with null value for numeric types and struct types. So if we still need to have the possibility of checking for null for numeric types and structs, we need to define them as Nullable types.
Using the code?
So let us start by looking at how we can use null with reference types. Let us have a simple class and null will represent the absence of value.?
class A
{
public void Display()
{
Console.WriteLine("Displaying from Class A.");
}
}
static void Main(string[] args)
{
// For reference type variable, it is possible to check for null values
A a = null;
if (a == null)
{
a = new A();
//use 'a' here
a.Display();
// with refeernce types it is also possible to assign null to them.
a = null;
}
}
How to create and use Nullable types
If this same entity would have need defined as an struct then it would not be possible to use null with it.
struct B
{
public void Display()
{
Console.WriteLine("Displaying from Struct B.");
}
}
static void Main(string[] args)
{
// For struct types it is not possible to have a null/assignment or check
// Below line will generate a compile error.
// B b = null;
}
So if we still need the object of class B to have the possibility of having null representing a null value then we need to create a Nullable type for B. A Nullable type is used to represent a possible missing value of a type.?
A Nullable type can be created as Nullable<T>. The T here is the type that cannot have null values. The type Nullable<T> will either have a null value or any possible value that T can have. So let us define a Nullable type for the above mentioned struct and see how we can use it.
static void Main(string[] args)
{
// SO to use null with string we need to create a nullabe type if B
Nullable<B> b2 = null;
if (b2 == null)
{
b2 = new B();
//use 'b' here
b2.Value.Display();
// Now with struct it is also possible to assign null since it is nullable
b2 = null;
}
}
And if we need to create a Nullable int then we can do it as
static void Main(string[] args)
{
// Numeric types should also be defined nullable if we need to use null
Nullable$ltint> i = null;
if (i == null)
{
i = 5;
Console.WriteLine(i.ToString());
}
}
Shorthand for using Nullable types
Instead of writing Nullable<T> every time, we can use the shorthand version for creating The Nullable types as T? t. Following code snippet shows how to use the shorthand notation to create the Nullable types.
static void Main(string[] args)
{
// Short hand for using nullable types
int? i2 = null;
if (i2 == null)
{
i2 = 15;
Console.WriteLine(i2.ToString());
}
}
How is Nullable defined
The Nullable type is defined as a struct and this itself is a value type. Following code snippet shows the major properties and functions that we might be needing if we are using a Nullable type.
领英推荐
public struct Nullable<T> where T : struct
{
public bool HasValue { get; }
public T Value { get; }
public T GetValueOrDefault();
public T GetValueOrDefault(T defaultValue);
public override string ToString();
}
The HasValue property will return true if the current Nullable<T> object has a value otherwise it will return false.
The Value property will return the value of the Nullable<T> object if the Nullable<T>.HasValue is true. if HasValue returns false then it will throw an exception.
The first version of GetValueOrDefault will return the value of the contained T type if it has some value otherwise it will return the T's default value. The second version will return the specified default value(passed as parameter) if the Nullable<T> has no value.
And finally the ToString method will return the string representation of the value contained in type T.
Conversion between Nullable and non-Nullable types
Once we create a Nullable type, we will find our self in need to convert it to and from the actual type. To understand how this conversion works, we need to look into how the conversion operators are defined in the Nullable type.
public struct Nullable<T> where T : struct
{
public static explicit operator T(T? value);
public static implicit operator T?(T value);
}
So looking at the above function declarations, it is clear that Conversion from T to Nullable is implicit but Conversion from Nullable to T is explicit.
static void Main(string[] args)
{
// Converting To and From Nullable types
int? i3 = null;
int i4 = 50;
// Conversion from T to Nullable is implicit
i3 = i4;
// Converstion from Nullable to T is explicit
i4 = (int)i3;
}
Boxing and Unboxing Nullable types
If we perform boxing on a T? then the the T will be boxed and not the T?. This optimization is provided by the C# itself. Also we can unbox any value and get the Nullable type in return. This is useful when we need to check for NULL after unboxing. Following code snippet shows how the boxing and unboxing works in unison with Nullabletypes.?
static void Main(string[] args)
{
// Boxing a nullable
DateTime? dt = null;
dt = DateTime.Now;
// this will store DateTime and not DateTime?
object o = dt;
// Unboxing to a nullable
DateTime? dt2 = o as DateTime?;
if (dt2 != null)
{
Console.WriteLine(dt2.Value.ToString());
}
}
Null Coalescing Operator
We have already discussed the GetValueOrDefault function above which will return the value of the contained T type if it has some value otherwise it will return the default value. This same thing can be achieved by using the Null coalescing operator ??. This operator is a syntactic sugar to perform the same operation as GetValueOrDefault(T defaultValue).
static void Main(string[] args)
{
// Coalascing operator
int? j = null;
int? k = 54;
int result1 = j ?? 0;
int result2 = k ?? 0;
// this will print 0 and 54
Console.WriteLine("result1 = {0}, result2 = {1}", result1, result2);
}
Operators and Nullable types
Before we finish let us look at the use of operators with Nullable types i.e. Operator lifting.the concept of operator lifting is that if we use Nullable types with operators then it could use the operators on underlying types but once it has checked for null value. Though we can use on Nullable types as if we are using it for the containing types, there are some things to be kept in mind.
Point of Interest
In this small article, I have tried to talk about the Nullable types in C#. We have seen the basic concepts and usage principles of Nullable types.