Nullable Types in C#.NET






4.87/5 (66 votes)
This article explains the details and use of Nullable Type in C#.NET.
Abstract
This article will help you to understand the Nullable
type implementation in C#. This article also explains about Coalescing Operator and how CLR has special support for Nullable
value type.
Introduction
As we all know, a value type variable cannot be null
. That's why they are called Value Type. Value type has a lot of advantages, however, there are some scenarios where we require value type to hold null
also.
Consider the following scenario:
Scenario 1: You are retrieving nullable integer column data from database table, and the value in database is null
, there is no way you can assign this value to an C# int
.
Scenario 2: Suppose you are binding the properties from UI but the corresponding UI don't have data. (For example, model binding in ASP.NET MVC or WPF). Storing the default value in model for value type is not a viable option.
Scenario 3: In Java, java.Util.Date
is a reference type, and therefore, the variable of this type can be set to null
. However, in CLR, System.DateTime
is a value type and a DateTime
variable cannot be null
. If an application written in Java wants to communicate a date/time to a Web service running on the CLR, there is a problem if the Java application sends null
because the CLR has no way to represent this and operate on it.
Scenario 4: When passing value type parameter to a function, if the value of parameter is not known and if you don't want to pass it, you go with default value. But default value is not always a good option because default value can also be a passed parameter value, so, should not be treated explicitly.
Scenario 5: When deserializing the data from XML or JSON, it becomes difficult to deal with the situation if the value type property expects a value and it is not present in the source.
Likewise, there are many scenarios we faced in our day to day life.
To get rid of these situations, Microsoft added the concept of Nullable
types to the CLR. To Understand this, have a look at the logical definition of System.Nullable<T>
Type:
(Please note that below code snippet is the logical definition for illustration purposes only. Taken from "CLR Via C#, 3rd edition", written by Jeffrey Richter)
[Serializable, StructLayout(LayoutKind.Sequential)]
public struct Nullable<T> where T : struct
{
// These 2 fields represent the state
private Boolean hasValue = false; // Assume null
internal T value = default(T); // Assume all bits zero
public Nullable(T value)
{
this.value = value;
this.hasValue = true;
}
public Boolean HasValue { get { return hasValue; } }
public T Value
{
get
{
if (!hasValue)
{
throw new InvalidOperationException(
"Nullable object must have a value.");
}
return value;
}
}
public T GetValueOrDefault() { return value; }
public T GetValueOrDefault(T defaultValue)
{
if (!HasValue) return defaultValue;
return value;
}
public override Boolean Equals(Object other)
{
if (!HasValue) return (other == null);
if (other == null) return false;
return value.Equals(other);
}
public override int GetHashCode()
{
if (!HasValue) return 0;
return value.GetHashCode();
}
public override string ToString()
{
if (!HasValue) return "";
return value.ToString();
}
public static implicit operator Nullable<T>(T value)
{
return new Nullable<T>(value);
}
public static explicit operator T(Nullable<T> value)
{
return value.Value;
}
}
From the above definition, you can easily make out that:
Nullable<T>
type is also a value type.Nullable
Type is ofstruct
type that holds a value type (struct
) and aBoolean
flag, namedHasValue
, to indicate whether the value isnull
or not.- Since
Nullable<T>
itself is a value type, it is fairly lightweight. The size ofNullable<T>
type instance is the same as the size of containing value type plus the size of aboolean
. - The
nullable
types parameterT
isstruct
, i.e., you can usenullable
type only with value types. This is quite ok because reference types can already benull
. You can also use theNullable<T>
type for your user definedstruct
. Nullable
type is not an extension in all the value types. It is astruct
which contains a generic value type and aboolean
flag.
Syntax and Usage
To use Nullable
type, just declare Nullable
struct
with a value type parameter, T
, and declare it as you are doing for other value types.
For example:
Nullable<int> i = 1;
Nullable<int> j = null;
Use Value
property of Nullable
type to get the value of the type it holds. As the definition says, it will return the value if it is not null
, else, it will throw an exception. So, you may need to check for the value being null
before using it.
Console.WriteLine("i: HasValue={0}, Value={1}", i.HasValue, i.Value);
Console.WriteLine("j: HasValue={0}, Value={1}", j.HasValue, j.GetValueOrDefault());
//The above code will give you the following output:
i: HasValue=True, Value=5
j: HasValue=False, Value=0
Conversions and Operators for Nullable Types
C# also supports simple syntax to use Nullable
types. It also supports implicit conversion and casts on Nullable
instances. The following example shows this:
// Implicit conversion from System.Int32 to Nullable<Int32>
int? i = 5;
// Implicit conversion from 'null' to Nullable<Int32>
int? j = null;
// Explicit conversion from Nullable<Int32> to non-nullable Int32
int k = (int)i;
// Casting between nullable primitive types
Double? x = 5; // Implicit conversion from int to Double? (x is 5.0 as a double)
Double? y = j; // Implicit conversion from int? to Double? (y is null)
You can use operators on Nullable
types the same way you use it for the containing types.
- Unary operators (++, --, -, etc) returns
null
if theNullable
types value is set tonull
. - Binary Operator (+, -, *, /, %, ^, etc) returns
null
if any of the operands isnull
. - For Equality Operator, if both operands are
null
, expression is evaluated totrue
. If either operand isnull
, it is evaluated tofalse
. If both are notnull
, it compares as usual. - For Relational Operator (>, <, >=, <=), if either operand is
null
, the result isfalse
and if none of the operands isnull
, compares the value.
See the example below:
int? i = 5;
int? j = null;
// Unary operators (+ ++ - -- ! ~)
i++; // i = 6
j = -j; // j = null
// Binary operators (+ - * / % & | ^ << >>)
i = i + 3; // i = 9
j = j * 3; // j = null;
// Equality operators (== !=)
if (i == null) { /* no */ } else { /* yes */ }
if (j == null) { /* yes */ } else { /* no */ }
if (i != j) { /* yes */ } else { /* no */ }
// Comparison operators (< > <= >=)
if (i > j) { /* no */ } else { /* yes */ }
The Coalescing Operator
C# provides you quite a simplified syntax to check null
and simultaneously assign another value in case the value of the variable is null
. This can be used in Nullable types as well as reference types.
For example, the code below:
int? i = null;
int j;
if (i.HasValue)
j = i.Value;
else
j = 0;
//The above code can also be written using Coalescing operator:
j = i ?? 0;
//Other Examples:
string pageTitle = suppliedTitle ?? "Default Title";
string fileName = GetFileName() ?? string.Empty;
string connectionString = GetConnectionString() ?? defaultConnectionString;
// If the age of employee is returning null
// (Date of Birth might not have been entered), set the value 0.
int age = employee.Age ?? 0;
//The Coalescing operator is also quite useful in aggregate function
//while using linq. For example,
int?[] numbers = { };
int total = numbers.Sum() ?? 0;
// Many times, it is required to Assign default, if not found in a list.
Customer customer = db.Customers.Find(customerId) ?? new Customer();
//It is also quite useful while accessing objects like QueryString,
//Session, Application variable or Cache.
string username = Session["Username"] ?? string.Empty;
Employee employee = GetFromCache(employeeId) ?? GetFromDatabase(employeeId);
You can also chain it, which may save a lot of coding for you. See the example below:
// Here is an example where a developer is setting the address of a Customer.
// The business requirement says that:
// (i) Empty address is not allowed to enter
// (Address will be null if not entered). (ii) Order of precedence of
// Address must be Permanent Address which if null, Local Address which if null,
// Office Address.
// The following code does this:
string address = string.Empty;
string permanent = GetPermanentAddress();
if (permanent != null)
address = permanent;
else
{
string local = GetLocalAddress();
if (local != null)
address = local;
else
{
string office = GetOfficeAddress();
if (office != null)
address = office;
}
}
//With Coalescing Operator, the same can be done in a single expression.//
string address = GetPermanentAddress() ?? GetLocalAddress()
?? GetOfficeAddress() ?? string.Empty;
The code above with Coalescing operator is far easier to read and understand than that of a nested if else
chain.
Boxing and UnBoxing of Nullable types
Since I have mentioned earlier that the Nullable<T>
is still a value type, you must understand performance while boxing and unboxing of Nullable<T>
type.
The CLR executes a special rule to box and unbox the Nullable
types. When CLR is boxing a Nullable
instance, it checks to see if the value is assigned null
. In this case, CLR does not do anything and simply assigns null
to the object
. If the instance is not null
, CLR takes the value and boxes it similar to the usual value type.
While unboxing to Nullable
type, CLR checks If an object
having its value assigned to null
. If yes, it simply assigns the value of Nullable
type to null
. Else, it is unboxing as usual.
// Boxing Nullable<T> is null or boxed T
int? n = null;
Object o = n; // o is null
Console.WriteLine("o is null={0}", o == null); // results to "True"
n = 5;
o = n; // o refers to a boxed Int32
Console.WriteLine("o's type={0}", o.GetType()); // results to "System.Int32"
// Create a boxed int
Object o = 5;
// Unbox it into a Nullable<int> and into an int
int? a = (Int32?) o; // a = 5
int b = (Int32) o; // b = 5
// Create a reference initialized to null
o = null;
// "Unbox" it into a Nullable<int> and into an int
a = (int?) o; // a = null
b = (int) o; // NullReferenceException
Calling GetType() for Nullable Type
When calling GetType()
for Nullable<T>
type, CLR actually lies and returns the Type the Nullable
type it holds. Because of this, you may not be able to distinguish a boxed Nullable<int>
was actually a int
or Nullable<int>
.
See the example below:
int? i = 10;
Console.WriteLine(i.GetType()); // Displays "System.Int32" instead of "System.Nullable<Int32>"
Points of Interest
Note that I haven't discussed the details of memory allocation and object creation while boxing and unboxing to keep the article focused to Nullable
types only. You may Google it for details about boxing and unboxing.
Conclusion
Since Nullable
Type is also a value type and fairly lightweight, don't hesitate to use it. It is quite useful in your data driven application.
References
- CLR via C#, 3rd edition: Jeffrey Richter
- http://msdn.microsoft.com/en-us/library/1t3y8s4s.aspx
- http://msdn.microsoft.com/en-us/library/2cf62fcy%28VS.80%29.aspx
History
- 1st November, 2011: Version 1.0