C#: Safe navigation avoiding NULLs

Groovy has a safe navigation operator “?.” which works just like the dot operator except that it returns nulls instead of throwing NullPointerExceptions when the object on the left is null.

Let’s see you want to do the following:

a.b.c;

But a could be null or a.b could be null, then you have to write something like this:

a == null ? null : a.b == null ? null : a.b.c;

This is getting much longer. If you actually allow your users to write code to extend your functionality, handling those nulls really becomes a pain. But in Groovy, you could just use the safe navigation operator to write the following:

a?.b?.c;

A little bit shorter than the one before… Well, now it’s great if you’re programming in Groovy. If you’re programming in C#, it just makes you wish you could use it…

Let’s see what can be done to get something similar (of course we can’t make it as compact as the Groovy syntax).

Let’s first start without checking for nulls:

using System;

namespace Playground
{
    public static class NullTest
    {
        public static void Main()
        {
            var a5 = new ClassA {B = new ClassB {C = new ClassC {S = "Hello"}}};

            Console.WriteLine(a5.B.C.S.Length);
        }
    }

    internal class ClassA
    {
        internal ClassB B { get; set; }
    }

    internal class ClassB
    {
        internal ClassC C { get; set; }
    }

    internal class ClassC
    {
        internal string S { get; set; }
    }
}

If instead of having a5 you have a4:

var a4 = new ClassA {B = new ClassB {C = new ClassC()}};

You’ll get a NullReferenceException because the string S is not initialized:

System.NullReferenceException was unhandled
HResult=-2147467261
Message=Object reference not set to an instance of an object.
Source=Playground
StackTrace:
at Playground.NullTest.Main() in d:\Software\Visual Studio 2008\Projects\Playground\Playground\Program.cs:line 11
at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()

And instead of a4 you could have any of those:

ClassA a1 = null;
var a2 = new ClassA();
var a3 = new ClassA {B = new ClassB()};

And you’d also get a NullReferenceException. So you need to end up writing something like:

a4 == null ? 0 : a4.B == null ? 0 : a4.B.C == null ? 0 : a4.B.C.S == null ? 0 : a4.B.C.S.Length

So what do we need to make it better ? First you need some kind of method which allows you to say return the property B of a4 is a4 is not null otherwise return something. Right now we’ll say that it’s fine to return null if a4 is null.

Since we want it to work for all types but do not want to end up loosing all type information because we handle everything as an object, we need to use some generics. So our method would look like:

TOut NullSafe<TIn, TOut>() {}

Since I’d rather have:

a4.NullSafe(...)

than:

NullSafe(a4, ...)

let’s make it an extension method:

    internal static class Nullify
    {
        public static TOut NullSafe<TIn, TOut>(this TIn obj, ...)
        {
        }
    }

First we need to check whether obj is null. If we just compare it with null, you might see that you get a warning saying that it’s a possible compare of value type with null. To do it properly, you will need to check it this way:

if (EqualityComparer<TIn>.Default.Equals(obj, default(TIn)))

If it is true, just return the default for TOut otherwise we’ll return a4.B.

Now, if you do not only want to support properties like a4.B but also indexers, method calls, operators or other kinds of expressions, it’s better to provide a lambda expression like:

a => a.B

This gives you maximum flexibility. It then looks like this:

    internal static class Nullify
    {
        public static TOut NullSafe<TIn, TOut>(this TIn obj, Func<TIn, TOut> memberAction)
        {
            return (EqualityComparer<TIn>.Default.Equals(obj, default(TIn))) ? default(TOut) : memberAction(obj);
        }
    }

And you can use it like this:

a4.NullSafe(a => a.B)

So instead of:

a4 == null ? 0 : a4.B == null ? 0 : a4.B.C == null ? 0 : a4.B.C.S == null ? 0 : a4.B.C.S.Length

you’d have:

a4.NullSafe(a => a.B).NullSafe(b => b.C).NullSafe(c => c.S).NullSafe(s => s.Length)

It’s not that much shorter but it does look less complex.

If instead of Length you had something which returns an object which could be null and instead of null you want to return some other default value, you can extend the class like this:

    internal static class Nullify
    {
        public static TOut NullSafe<TIn, TOut>(this TIn obj, Func<TIn, TOut> memberAction)
        {
            return (EqualityComparer<TIn>.Default.Equals(obj, default(TIn))) ? default(TOut) : memberAction(obj);
        }

        public static TIn NullDefault<TIn>(this TIn obj, TIn defaultValue)
        {
            return EqualityComparer<TIn>.Default.Equals(obj, default(TIn)) ? defaultValue : obj;
        }
    }

You then just need to add a call to NullDefault in the end e.g.:

xxx.NullDefault("not set");

If xxx is an expression return a string in the end.

Leave a Reply

Your email address will not be published.