Handling nullables efficiently

September 20, 2013

...

I earlier wrote about Handling null checks efficiently by using extension methods to make our code more terse, not cluttered by null check blocks. When we use nullables (Nullable<T>), we come across a similar construction in our code. We have to check if the nullable has a value before accessing the value. If we try to reference the value of a nullable, and it has no value, we get an InvalidOperationException. This is quite similar to getting an NullReferenceException if trying to access a reference which is null.

Let’s take the example code from my previous post, and add an enum for Nationality. Then, we add a Citizenship property with type Nullable<Nationality> to the Person class. Like so:

enum Nationality
{
    No,Se,Dk,Uk,Us
}
internal class Person
{
    public string Name { get; set; }
    public Address Address { get; set; }
    public Nationality? Citizenship { get; set; }
}

If we then create a Person object that has the Citizenship property not set, we get an exception if we try to dereference it:

var person = new Person {Name = "Jackie Chiles"};
Console.Write("Citizenship:");
Console.Write(person.Citizenship.Value);

Handling nullables efficiently

What we can do here, is that we can create an .IfHasValue and .DoIfHasValue extension methods that are analogous to the .IfNotNull and .DoIfNotNull extension methods in the previous blog post:

public static class FlowControlExtensions
{
    [DebuggerStepThrough]
    public static void DoIfHasValue<T>(this T? obj, 
        Action<T> action, bool doContinue = true) where T : struct
    {
        if (obj.HasValue)
        {
            action(obj.Value);
        }
        if (doContinue)
            return;
        ThrowInvalidOperationException<T, T>();
    }
    [DebuggerStepThrough]
    public static TResult IfHasValue<T, TResult>(this T? obj, 
        Func<T, TResult> func, bool doContinue = true, 
        TResult defaultValue = default(TResult)) where T : struct
    {
        if (obj.HasValue)
        {
            return func(obj.Value);
        }
        return doContinue ? defaultValue : 
            ThrowInvalidOperationException<T, TResult>();
    }
    [DebuggerStepThrough]
    private static TResult ThrowInvalidOperationException<T, TResult>()
    {
        throw new InvalidOperationException(string.Format(
            "Tried to access value of nullable of {0}, but it had no value",
                typeof(T).FullName));
    }
}

Now we can use this to prevent our code from throwing an exception, and return a default value instead:

 var person = new Person {Name = "Jackie Chiles" };
 Console.Write("Citizenship:");
 Console.Write(person.Citizenship.IfHasValue(c => c.ToString()));

If we wish to give it another default value, we can simply do this like so:

Console.Write(person.Citizenship
    .IfHasValue(c => c.ToString(), defaultValue: "(unknown)"));

You can find the code on GitHub.


Profile picture

Written by Vidar Kongsli who is a software professional living in Oslo, Norway. Works as a consultant, system architect and developer at Bredvid. You should follow him on Twitter