C# 2.0 Nullable Types

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:

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:

What we see in the above example is the use of a try…catch 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:

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:

In the example above, an enum definition has been added which defines a NULL element; GetAge has also been rewritten to use ReturnValuesenum 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:

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:

A nullable indicator may also be applied to any struct. The example below shows two declarations, one with a standard Pointstruct 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 CommandTypeenum:

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:

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:

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 structReal which encapsulates a 32 bit integer:

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.

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:

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 Realstruct 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:

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.