Handling null checks efficiently

September 17, 2013

...

At the QCon Conference in London in 2009, Sir C.A.R. Hoare apologized for inventing the null reference back in 1965 for the Algol W programming language. He called it his billion-dollar mistake.

Checking for null is one of the constructs that we use very often in our C# code. If we have a lot of them, they tend to clutter our code and make it harder to read and understand the business logic that our code implements. In this post I will use extension methods to make a bit more terse null checks to handle a few different code cases. It is by no means a novel idea, you can find various references on the web. Anyway, let’s give it a try.

Let’s consider this simple object model:

    +--------+    +-----------------+
    | Person |    | Address         |
    |--------|+-->|-----------------|
    | Name   |    | City            |
    +--------+    | PostalCode      |
                  | Street          |
                  | WriteToConsole()|
                  | ToString()      |
                  +-----------------+

We have a Person class which contains a reference to an Address class. When our program are trying to reference properies of the Address objects via the Person object, we have to consider that the reference to the Address object might be null. Here’s an example of what we would start out with:

var person = new Person {Name = "Cosmo Kramer"};
Console.WriteLine("Person: {0}", person.Name);
Console.WriteLine("Address:");
person.Address.WriteToConsole();

Of course, if person.Address is null, this will give us a NullReferenceExecption. So, one way to handle this, is to check for null before calling WriteToConsole, like so:

var person = new Person { Name = "Cosmo Kramer" };
Console.WriteLine("Person: {0}", person.Name);
Console.WriteLine("Address:");
if (person.Address != null)
{
    person.Address.WriteToConsole();
}

This is good, but if we have a lot of such null checks like these, our code will be quite cluttered. One simple solution would be to create an extension method that let us give a code to be called if the object called on is not null. Consider the following method:

public static class FlowControlExtensions
{
    public static void DoIfNotNull<T>(this T obj,
        Action<T> action) where T : class
    {
        if (obj != null)
        {
            action(obj);
        }
    }
}

If we use this new method, our code now looks like this:

var person = new Person { Name = "Cosmo Kramer" };
Console.WriteLine("Person: {0}", person.Name);
Console.WriteLine("Address:");
person.Address.DoIfNotNull(a => a.WriteToConsole());

Then we have a little more terse code. In this example, the execution will continue if the Address property of the person object is null. In some situations, nulls are not acceptable and we would like to throw an exception. For example:

var person = new Person { Name = "Cosmo Kramer" };
Console.WriteLine("Person: {0}", person.Name);
Console.WriteLine("Address:");
if (person.Address != null)
{
    person.Address.WriteToConsole();
}
else
{
    throw new NullReferenceException();
}

One of the annoying things about the standard .NET usage of the NullReferenceException is that it’s error message does not contain any hints about which object was null. Let’s take the opportunity to make it little better. Let’s change our DoIfNotNull method implementation:

public static class FlowControlExtensions
{
    [DebuggerStepThrough]
    public static void DoIfNotNull<T>(this T obj,
        Action<T> action, bool doContinue = true) where T : class
    {
        if (obj != null)
        {
            action(obj);
        }
        if (doContinue) return;
        throw new NullReferenceException(
            string.Format("Reference of type {0} was null.",
                typeof (T).FullName));
    }
}

We have added a default parameter to the method signature. This acts as a flag to set whether or not to continue the execution if null occurs, or throw an exception. Also, we take care to construct the error message so that it gives us the type of the reference being null, which would help us a bit investigating the error. Also note that I added the DebuggerStepThroughAttribute which means that the debugger will not step into this method, so that it will actually stop at the same code line as it would before we introduced our extension method. In the situation that we wish to throw an exception if the reference is null, the example now looks like so:

var person = new Person { Name = "Cosmo Kramer" };
Console.WriteLine("Person: {0}", person.Name);
Console.WriteLine("Address:");
person.Address.DoIfNotNull(a => a.WriteToConsole(), doContinue : false);

This code will now throw an exception with the message “Reference of type Address was null.”

So, now we have the possibility to run any code snippet that fits in an Action<T> if the reference is not null. Let’s try to handle the situation where we wish to evaluate an expression instead. Consider the following example:

var person = new Person { Name = "Cosmo Kramer" };
Console.WriteLine("Person: {0}", person.Name);
Console.WriteLine("Address:");
Console.WriteLine(person.Address.ToString());

The last line in this example will throw a NullReferenceException if the Address property is not set. Again, we will try to handle this situation using an extension method:

public static class FlowControlExtensions
{
    [DebuggerStepThrough]
    public static TResult IfNotNull<T,TResult>(this T obj, 
        Func<T, TResult> func) where T : class
    {
        if (obj != null)
        {
            return func(obj);
        }
        return default(TResult);
    }
}

Notice that if the object reference in fact is null, we return default(TResult) from the extension method. Now, we achieve to make our code run even though we have a null reference:

var person = new Person { Name = "Cosmo Kramer" };
Console.WriteLine("Person: {0}", person.Name);
Console.WriteLine("Address:");
Console.WriteLine(person.Address.IfNotNull(a => a.ToString()));

One improvement we might want to do here, is to make our little program make it explicit if the address is null. Right now, it will only print a blank line. Let’s make a small change to our extension method:

public static class FlowControlExtensions
{
    [DebuggerStepThrough]
    public static TResult IfNotNull<T,TResult>(this T obj,
        Func<T, TResult> func, 
        TResult defaultValue = default(TResult)) where T : class
    {
        if (obj != null)
        {
            return func(obj);
        }
        return defaultValue;
    }
}        

Here we introduced a named parameter which by default is default(TResult). If we wish, we can now override this to give the value returned by the extension method in case of null:

var person = new Person { Name = "Cosmo Kramer" };
Console.WriteLine("Person: {0}", person.Name);
Console.WriteLine("Address:");
Console.WriteLine(person.Address.IfNotNull(a => a.ToString(), defaultValue : "(unknown)"));

Before we call it a day, let’s handle one final scenario. Say that we actually want to sustain the default behavior in case of null, throw a NullReferenceException. This is trivial to do, but let’s take the opportunity to give a more helpful error message than the one we get by default. For this, we will put expression trees to work.

Instead of having our extension method take a Func<T,TResult> parameter, we will make it an Expression<Func<T,Result>>. What we achieve with this is to be able to traverese the expression tree to look for the property or method on the reference being null that we were trying to call. This will give us helpful hints about which object reference was actually null. Also, let’s introduce a parameter that enables us to say that we wish to throw an exception in case of an error. A simple implementation looks like this:

public static class FlowControlExtensions
{
    [DebuggerStepThrough]
    public static TResult IfNotNull<T,TResult>(this T obj, 
        Expression<Func<T, TResult>> func, 
        TResult defaultValue = default(TResult), 
        bool doContinue = true) where T : class
    {
        if (obj != null)
        {
            return func.Compile()(obj);
        }
        if (doContinue) return defaultValue;
        throw new NullReferenceException(
            string.Format("Reference of type {0} was null.",
                typeof (T).FullName));
    }
}

We can now use this method to throw an exception in case of null:

var person = new Person { Name = "Cosmo Kramer" };
Console.WriteLine("Person: {0}", person.Name);
Console.WriteLine("Address:");
Console.WriteLine(person.Address.IfNotNull(a => a.ToString(), doContinue: false));

If the Address property is null, it will throw a NullReferenceException with the message ‘Reference of type Address was null.’.

Handling null checks efficiently-2

Let’s try to see if we can improve the error message to give us a little more information. First, let’s introduce a private class which will traverse the expression tree to look for the method or property referenced:

public static class FlowControlExtensions
{
    private class MemberNameCollector : ExpressionVisitor
    {
        public string MemberName = "Unknown";
        private readonly string _parameterName;
        public MemberNameCollector(string parameterName)
        {
            _parameterName = parameterName;
        }
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (node.Object != null 
                && node.Object.NodeType == ExpressionType.Parameter
                && _parameterName == node.Object.ToString())
            {
                MemberName = node.Method.Name + "(...)";
            }
            return base.VisitMethodCall(node);
        }
        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.NodeType == ExpressionType.MemberAccess 
                && node.Expression.ToString() == _parameterName)
            {
                MemberName = node.Member.Name;
            }
            return base.VisitMember(node);
        }
    }
}

We can now use this class to alter the implementation of our IfNotNull extension method:

public static class FlowControlExtensions
{
    [DebuggerStepThrough]
    public static TResult IfNotNull<T,TResult>(this T obj,
        Expression<Func&ltT, TResult>> func, 
        TResult defaultValue = default(TResult), 
        bool doContinue = true) where T : class
    {
        if (obj != null)
        {
            return func.Compile()(obj);
        }
        if (doContinue) return defaultValue;
        var parameterName = func.Parameters.First().Name;
        var parameterType = typeof (T).FullName;
        var visitor = new MemberNameCollector(parameterName);
        visitor.Visit(func.Body);
        throw new NullReferenceException(
            string.Format("Tried to reference .{0} on parameter with name '{1}' of type {2}, but it was  null",
            visitor.MemberName, parameterName, parameterType));
    } 
}

We now get a little more information in our NullReferenceException message stating “Tried to reference .ToString(…) on parameter with name ‘a’ of type Address, but it was null”. A little more helpful…

Handling null checks efficiently-3

Summary

We have used extension methods to handle several null handling situations in a terse way. Here’s what we have covered:

Example Description
person.Address.DoIfNotNull(a => a.WriteToConsole()); Runs a statement. Continue execution in case of null.
person.Address.DoIfNotNull(a => a.WriteToConsole(), doContinue : false); Runs a statement. Throws a NullReferenceException in case of null. Somewhat improved exception message compared to OOTB.
person.Address.IfNotNull(a => a.ToString()); Evaluates an expression. Returns default(T) in case of null.
person.Address.IfNotNull(a => a.ToString(), defaultValue : "(unknown)"); Evaluates an expression. Returns "(unknown)" in case of null.
person.Address.IfNotNull(a => a.ToString(), doContinue: false); Evaluates an expression. Throws a NullReferenceException in case of null. Improved exception message compared to OOTB.

If you want to try this out yourself, it is quite straightforward to copy and paste the code. You can also find the example 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