Nullable Types in C#: Basic Introduction

Introduction

The Value type objects are faster for the accessing and operations as compared to their reference counterpart. This makes them as a preferred choice for the data storage in the program. However there are certain limitations while using the value type objects in the application. This article gives a quick view in to the problems faced with the value type objects and the solution to the problems. This beautiful facility is introduced by Microsoft as an concept called "Nullable Types" in the C# 2.0.

In this article I will focus to answer two questions mainly those are:

  1. Why we need Nullable Types?
  2. What are the Nullable Types?

a. Why need Nullable type.

1. What will be the default value for the value type object?

Assigning a default value to a value type object is really a brainstorming process. Since this involves the use of the probability matrix, through which one can evaluate the possible values that a value type object can be assigned during it's whole life span. Apart from finding the values that a object can have during it's life span we also need to lay down a standard to consider some arbitrary value that lie outside the probability matrix; as the default value. This forces one rule on the project standards to consider the designated value as a default value and then force to follow the rule on the people working on the project.

Apart from this working and forced standards limitation; this default value occupies the space in the value matrix as default and unusable value limiting the future value for the object.

Let's focus this with an example.

I have a structure about the student information I can write the structure as:

public struct student{
    private int m_RollNumber; // Can range from 0 -- 100
    private string m_FullName ; // the non blank string name.
    ..
    ..
    public student( int RollNumber, string FullName,.....){
    //Initialize the members of the new object
    this.m_RollNumber = RollNumber ;
    this.m_FullName = FullName;
    ..
    ..
}

Even though I can provide the value for the roll number in the constructor, The C# still allows me to call the default constructor with no arguments. The default constructor will initialize the m_RollNumber to the zero and the m_FullName to the blank string.  Since m_RollNumber =0 is a valid value in the value matrix for the m_RollNumber. To denote the un-initialized m_RollNumber we need to assign a value to the m_Rollnumber that can be considered as default. Here since I assume that the value for the m_RollNumber must be between 0 - 100 through it's life span. Based on this analysis I can consider values that are either Negative ( <0) or Positive (>100) as an default value. 

Lets Say I decide the value 999 as my default for the roll number; I need to force this rule to check the value being 999 before any body use the m_RollNumber in the code, and also the rule to think the value as 999 as default and denoting the un-initialized m_RollNumber.

Thinking of the future impact when the roll number reaches the value 1000, the value 999 will be useless even though being a valid roll number.

2.  The Functions returning the Value type object can not denote about the success of the execution:

Consider I have a collection of students where I search for any particular roll number.

public class Students
{
    //The Internal array list maintaining the student list
    private ArrayList m_Students = new ArrayList(100);
    public Student GetStudentInfo(int RollNumber){
        //Iterate through the list to find the student with roll number RollNumber
        foreach( student stTemp in m_Students){
            //Check if the stTemp is the required student.
            If( stTemp.m_RollNumber == RollNumber){
                //if yes return the stTemp.
                Return stTemp ;
            }
        }
        //If no match found return the empty object.
        Return new Student();
    }
}

The implementation of this function will return an object of the Student structure in all the cases even though the function fails to find the particular student[Since returning a blank object is done to suppress the compiler error.]. The caller of the function is still in ambiguity of the validity of the value returned. This is due to the fact that we can never assign the "null" value to the value type object.

To work around this problem we need to go for the 'Out' parameters. The modified version of the function is as follows:

public bool GetStudentInfo( int RollNumber, out Student outStudent){
    //Initialize the outStudent object to a blank object.
    outStudent = new Student();
    //Iterate through the loop to find the student with roll number as RollNumber
    foreach( student stTemp in m_Students)
    {
        If( stTemp.m_RollNumber == RollNumber)
        {
            //If the required object found then
            //Save it to the out parameter and
            outStudent = stTemp ;
            //return true to indicate the success.
            Return true;
        }
    }
    //If object is not found then simply return the false to denote the failure
    Return false;
}

By calling this version of the function the caller has a way to find the validity of the returned object and in turn to check the success of the function execution through the returned Boolean value. However changing the function signature this way makes the function hard to understand and call.

To overcome the above mentioned problems with the default value and the return value from the function. We need some tactful way around this problem.

That's the reason Microsoft has introduced the Nullable Types in the C# 2.0.

b. What are the Nullable types?

Nullable Types are the same generic data type with a wrapper structures written around the generic data type. This wrapper structures allow assigning the value "null" to the value type objects even.

If any object of the generic type has the '?' attached to the data type while declaring the value object. It denotes the object is a Nullable object of that data type e.g. if I declare the RollNumber as

int? RollNumber = null ;

The RollNumber declared above is Nullable roll number that means assigning the value null to the Rollnumber is valid and completely acceptable. This reduces the burden to make some value as default and also since the function can return the Nullable types as a return type we have a way to return a null value through the object when the function fails.

An instance of the Nullable type has two public properties.

  • HasValue - A Boolean property denoting the validity of the value contained in the object.
  • Value - A value contained in the object.

The 'true' value for the HasValue property guarantees the validity of the value contained in the object which can be accessed through the Value property. A 'false' value for the HasValue indicates the invalid value contained by the object suggesting not to use the Value property.

An instance for which HasValue is false is said to be null. A null instance has an undefined value. Attempting to read the Value of a null instance causes a System.InvalidOperationException to be thrown.

Taking about the default values for the Nullable type we can say; Any instance of a Nullable type is said to have a default value when the HasValue is set to 'false' and the value property is undefined. This value is known as an null value of an Nullable type. While implicitly converting the 'null' value to any nullable type values the conversion results in the null value of the nullable type.

The following example shows the use of the Nullable type.

class Program
{
    //the Nullable Roll number.
    public int? RollNumber = null;
    static void Main(string[] args)
    {
        Program pTemp = new Program();
        //use the public member.
        pTemp.RollNumber = 100;
        //Write to console to check the object values.
        Console.WriteLine("Value in RollNumber is:{0} ,[HasValue]{1}", pTemp.RollNumber.Value, pTemp.RollNumber.HasValue);
        //test using the null.
        pTemp.RollNumber = null;
        Console.WriteLine("Value in RollNumber is:{0} ,[HasValue]{1}", pTemp.RollNumber.Value, pTemp.RollNumber.HasValue); //This line throws and InValidOperationException.
    }
}

Some rules govern the type conversion between the Nullable and non-Nullable types this will be covered in the next articles [With a modified version of the GetStudentInfo function].