Click here to Skip to main content
Click here to Skip to main content
Go to top

C# 2.0 Nullable Types

, 3 Oct 2005
Rate this:
Please Sign up or sign in to vote.
General instructions on the use of nullable types in C#.

Introduction

What is null?

The term null is an interesting programming concept in that it does not mean the same as zero or blank; rather, it often implies missing or otherwise undefined data and because of this, is frequently used as a flag in this scenario. Nullability refers to the ability of a data type to accept and appropriately handle NULL values.

Approaches to nullability

Since the dawn of programming, there existed a need to support nullability for value types; yet most general purpose programming languages have historically provided little or no support in this area. The obvious scenario is in the instance where a program interacts with a database. Because databases typically provide definable nullability on all types situations like the one highlighted below may occur:

//User object method

public object Age
{
  get
  {
     SqlCommand cmd = new SqlCommand(
        "select age from user where username = @UserName ", conn);
     cmd.Parameters.AddWithValue("UserName", name);
     return cmd.ExecuteScalar();
  }
}

//consuming object

public int GetAge(string name)
{
  //get user object from database
  User user = GetUser(name);
  int age = (int) user.Age; //this value might be null
  return age;

The example above declares a GetAge() method which takes a string name as parameter. The first instruction of this method retrieves a valid user based on the name provided by the name parameter. The second instruction calls the Age property of the retrieved user object to return the given user’s current age. The Age property performs a simple scalar operation to retrieve the current user’s age. As you can see, nullability presents an issue on both sides of the fence. Data publishers like the Age property must maintain generic interfaces to allow support for nulls or handle null values and pass back symbolic representations in such instances. On the consuming side, the situation highlighted above will leave the programmer with no way of telling whether the returned value from the database is null. If it is, the program will raise an exception. One quick fix to the problem is to allow and trap the exception as follows:

public int GetAge(string name)
{

  //get user object from database

  User user = GetUser(name);
  int age = 0;
  try
  {
      age = (int)user.Age; //this value might be null
  }

  catch (Exception ex)
  {
      age = -1;
  }
  return age;

What we see in the above example is the use of a trycatch block to handle all instances where the Age property might return a null value.

Many approaches exist for handling nulls and value types without direct language support, but all have shortcomings. One approach is to use a distinguished value like -1 to indicate a null; unfortunately the strategy only works when an unused value can be identified. Hence the GetAge method can be rewritten as follows to apply this strategy:

public int GetAge(string name)
{
   //get user object from database
   User user = GetUser(name);

   object age = user.Age; //this value might be null
   if(age != null)
      return (int)age;
   else
      return - 1;
}

The above examples change the local variable age from an int type to a generic object type. Since the object type can handle null values age can be tested for null and returned only if a value exists. If there is no value, a -1 is returned to the caller as a symbolic representation of null.

One obvious problem with this approach is that the caller must have explicit knowledge of the meaning of -1; however, this can be easily mitigated by using a constant or enum. The example below illustrates this concept:

public enum ReturnValues : int
{
    NULL
}

public int GetAge(string name)
{
    //get user object from database
    User user = GetUser(name);

    object age = user.Age; //this value might be null
    if(age != null)
        return (int)age;
    else
        return (int)ReturnValues.NULL;
}

In the example above, an enum definition has been added which defines a NULL element; GetAge has also been rewritten to use ReturnValues enum for returning nulls rather than an arbitrary number.

Another approach is to maintain boolean null indicators in separate fields or variables, but this will only work well in languages that provide facility to return multiple values. The example below illustrates two approaches to this strategy, one for a language which provides native ability to return multiple results from a method, another for a language which does not:

//language constructs for multiple return types
public int GetAge(string name, out bool isNull)
{
    isNull = false; 
    //get user object from database
    User user = GetUser(name);

    object age = user.Age; //this value might be null
    if (age != null)
        return (int)age;
    else
    {
        isNull = true;
        return -1;
    }
}

//no language constructs for 
//multiple return types
public Hashtable GetAge(string name)
{
    //declare hashtable and HasValue key
    Hashtable retval = new Hashtable();
    bool isNull = true;
    retval.Add("HasValue",isNull);

    //get user object from database
    User user = GetUser(name);

    object age = user.Age; //this value might be null
    if (age != null)
    {
       retval.Add("value", (int)age);
       return retval;
    }
    else
    {
       retval["HasValue"] = false;
       return retval;
    }
}

A third approach is to use a set of user-defined nullable types, but this only works for a closed set of types:

public class NullableType
{
    public bool HasValue;
    public object Value;
}

//no language constructs for multiple return types
public NullableType GetAge(string name)
{
    //declare hashtable and HasValue key
    NullableType retval;
    retval.HasValue = true;
    //get user object from database
    User user = GetUser(name);

    object age = user.Age; //this value might be null
    if (age != null)
    {
        retval.Value = (int)age;
        return retval;
    }
    else
    {
        retval.HasValue = false;
        return retval;
    }
}

Introducing C# nullables

With C# 2.0 comes the introduction of the nullable type as a complete and integrated solution for the nullability issue on all forms of value types. A C# nullable type is essentially a structure that combines a value of the underlying type with a boolean null indicator. Similar to our NullableType class in the previous section, an instance of a nullable type has two public read-only properties: HasValue, of type bool, and Value, of the nullable type’s underlying type. HasValue is true for a non-null instance and false for a null instance. When HasValue is true, the Value property returns the contained value. When HasValue is false, an attempt to access the Value property throws an exception. Nullable types also possess a default constructor which accepts as an argument, an instance of the underlying type of that nullable. Underlying types are discussed later on.

Nullable types are constructed using the ? type modifier. This token is placed immediately after the value type being defined as nullable. For instance, if we were trying to define a uint in its nullable form, we would apply the ? after making the token uint?. The type specified before the ? modifier in a nullable type is called the underlying type of the nullable type. Any value type can be an underlying type; the example below illustrates defining nullables for the 13 built in types:

char? nullChar = null;
byte? nullByte = null;
sbyte? nullsbyte = null;
short? nullShort = null;
ushort? nullushort = null;
int? nullInt = null;
uint? nullUint = null;
long? nullLong = null;
ulong? nullulong = null;
float? nullFloat = null; 
double? nullDouble = null;
decimal? nullDecimal = null;
bool? nullBool = null;

A nullable indicator may also be applied to any struct. The example below shows two declarations, one with a standard Point struct and the other with the nullable version of the Point object:

Point point = new Point(10, 10);
Point? nullPoint = null;

nullPoint can also be instantiated by invoking the default constructor discussed earlier in this section:

nullPoint = new Point?(point);

Using the nullable version of an enum is no different. The example below instantiates to null, a CommandType enum:

CommandType? ct = null;

In the previous section, we declared a GetAge() method which took a string name as parameter. The first instruction of this method retrieves a valid user based on the name provided by the name parameter. The second instruction calls the Age property of the retrieved user object to return the given user’s current age. We also provided the definition of the Age property of the user object which performed a simple scalar operation to retrieve the current user’s age. The examples are listed below for recap:

//User object method
public object Age
{
    get
    {
        SqlCommand cmd = new SqlCommand(
            "select age from user where username = @UserName ", conn);
        cmd.Parameters.AddWithValue("UserName", name);
        return cmd.ExecuteScalar();
    }
}

//consuming object
public int GetAge(string name)
{
    //get user object from database
    User user = GetUser(name);

    int age = (int) user.Age; //this value might be null
    return age;
}

By utilizing nullable types, we can address the nullability requirements presented on both sides of the fence. Data publishers like the Age property need no longer maintain generic interfaces to allow support for nulls or handle null values and pass back symbolic representations in such instances. We can cast the results of ExecuteScalar to an int? and redefine the property signature to return int?. On the consuming side, the programmer can now use the HasValue property of int? to determine whether the returning value from the database is indeed null. The new version of the sample is listed below:

//part of User object
public int? Age
{
    get
    {
        SqlCommand cmd = new SqlCommand(
            "select age from user where username = @UserName ", conn);
        cmd.Parameters.AddWithValue("UserName", name);
        return (int?) cmd.ExecuteScalar();
    }
}

//consumming object
public string GetAge(string name)
{
    //get user object from database
    User user = GetUser(name);

    int? age = user.Age; //this value might be null
    if (age.HasValue)
    {
        return age.Value.ToString();
    }
    else
    {
        return "No age found for user";
    }
}

But wait there’s more: User defined nullable types

As if all this cool functionality wasn’t enough, you also have the ability to create your own user defined null value types, and the beauty of it is that you do not have to do any extra work because any struct or enum you create will automatically have a nullable version of itself that can be utilized anywhere you need it. The example below illustrates this by creating a simple struct Real which encapsulates a 32 bit integer:

public struct Real
{
    int internalVal;
    public Real(int realNumber)
    {
        internalVal = realNumber ;
    }
}

Once you have your user defined nullable type defined, you may use it as you would use any user defined type:

Real real = new Real(3);

You also have the ability to use the nullable version of your type, post fixed with the ?, just like the built-in nullables, but this cannot be accomplished by simply instantiating the nullable in the manner specified below:

Real? real = new Real?(3);

Attempting to compile the snippet below will produce a compiler error which states:

Cannot convert null to 'Real' because it is a value type

The rules do not change for your user-defined types. The nullable inferred from your struct definition requires a standard Real object as an argument to the constructor. Modifying the snippet above to accommodate this produces the following:

Real? real = new Real?(new Real(3));

User defined conversions may also be applied as with any struct type. The code below adds an implicit conversion from int to Real.

public static implicit operator Real(int realNumber)
{
    return new Real(realNumber);
}

Doing this gives you the approximate functional look and feel of the built-in nullables. Thus the real instance can be initialized in the following ways:

Real? real = new Real?(new Real(null));
real = 9;
real? = null;

Default values

The default value of a nullable type is an instance for which the HasValue property is false and the Value property is undefined. The default value is also known as the null value of the nullable type. An implicit conversion exists from the null literal to any nullable type, and this conversion produces the null value of the type.

Conversions

There are a number of conversions that can be applied to nullable types. From the previous section you have already seen that an implicit conversion exists from the null literal to any nullable type, and this conversion produces the null value of the type. This is illustrated with the simple statement int? x = null; setting a nullable type to a given literal is also the result of an implicit conversion from the underlying literals type to that of the nullable type. For instance the statement:

int? x = 5;

is really an implicit conversion from int to int?.

There is also the concept of lifted and nullable conversions, which allow for predefined conversions that operate on non-nullable value types to also be applied to nullable forms of those types. The following code snippet will cause the number 104 to be displayed in the console window.

int character = (int)'h';
Console.WriteLine(character);

This of course is a general conversion from type char to type int. The same number is displayed if the snippet is rewritten to use the int? nullable type.

int? character = (int?)'h';
Console.WriteLine(character);

Rewriting the sample to use char? and int? will still produce the same results:

char? c = 'h'; 
int? character = (int?)c;
Console.WriteLine(character);

The following diagram illustrates the conversion workflow specified above. The pattern it identifies can be applied across any lifted conversion between any two nullable types whose standard types have a predefined or user defined conversion.

Conversions between the nullable and standard version of a given value type vary based on direction. Converting from standard to nullable is always implicit whereas conversions from nullable back to standard is always explicit. The example below illustrates this:

int i = 10;
int? j = i; //implicit

i = (int)j; //explicit

Lifted operators

In the section titled user defined nullable types; we created an implicit operator that converted int to Real. We then utilized that operator on the nullable version of the Real struct with the instruction Real? real = 9;. Lifted operators work essentially in the same manner. They permit the predefined and user defined operators that work on the standard value types to also work on the nullable versions of those types.

Null coalescence

C# 2.0 introduces a new operator called the null coalescing operator denoted by double question marks (??). The null coalescing operator takes as arguments a nullable type to the left and the given nullable type’s underlying type on the right. If the instance is null, the value on the right is returned otherwise the nullable instance value is returned. Examine the example below:

//nullable instance is set to null
Line 1: int x = 10;
Line 2: int? j = null;
Line 3: int answer1 = j ?? x;
Line 4: Console.WriteLine(answer1);

//nullable instance now has value
Line 5: j = 100;
Line 6: int answer2 = j ?? 10;
Line 7: Console.WriteLine(answer2);

The variable j is of type nullable int and is initially set to null. The coalescing operator expression on line 3 will therefore evaluate to 10 since j has no value. Hence ten is the value of answer1 and will be printed to the console window. Line 5 sets j to 100, hence on line 6, the value will now evaluate to true on j. Consequently; answer will be set to 100 and 100 will be displayed on the console window.

The null coalescing operator is an easy way to test for null and presents an alternative value should the nullable not have one. The important rule concerning this is that, in the case of nullables, the instance or value on the right must be of the same type as the underlying type of the instance on the right. However the null coalescing operating is not limited to nullables in its application.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Edward Moemeka

United States United States
Hi I'm Edward Moemeka,
For more interesting articles about stuff check out my blog at http://moemeka.blogspot.com
To correspond, email me at edward.moemeka@synertry.com
To support my company, thus help me feed my family, check out our awesome online preview at www.synertry.com. Remember, its in alpha Wink | ;-)

Comments and Discussions

 
GeneralHm.. for custom types.. PinmemberCurtis W3-Oct-05 11:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 3 Oct 2005
Article Copyright 2005 by Edward Moemeka
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid