Have you ever had to write a code like this one?

var store = GetStore();
string postCode = null;

if (store != null && store.Address != null && store.Address.PostCode != null)
     postCode = store.Address.PostCode.ToString();

I’m sure you have. We’ve all been there.
The goal is to retrieve (or compute) a value, but in order to do that, we have to get through many intermediate objects, which could be in a default state. The aforementioned example is pretty easy. Sometimes, along the way, there are additional method calls, “as” conversions or collections. We have to handle properly each of them, which bloats further the code, making it less readable. There’s got to be a better way.

 

Conditional extensions

Many developers found a solution in conditional extensions. If you google “c# deep null check” you will find several implementations. Their names vary, but the idea behind them is the same:

public static TResult IfNotNull<TResult, TSource>(
    this TSource source,
    Func<TSource, TResult> onNotDefault)
    where TSource : class
{
    if (onNotDefault == null) throw new ArgumentNullException("onNotDefault");

    return source == null ? default(TResult) : onNotDefault(source);
}

As you can see, the extension returns a value of TResult type. The source object is required to obtain the result. If the former is null, we are unable to do that, thus the method returns default value of TResult type. If it’s not the case, we can safely invoke the onNotDefault delegate.
After adding IfNotNull extension to our project, the first example can be rewritten in the following way:

var postCode =
    GetStore()
        .IfNotNull(x => x.Address)
        .IfNotNull(x => x.PostCode)
        .IfNotNull(x => x.ToString());

It really pays off in more complex scenarios, i.e. when we have to call some methods along the way and store the results in temporary variables.

The IfNotNull extension can be improved in several ways. I would like to focus on 2 of them:

  • The aforementioned extension deals only with reference types. We can amend it to work with value types too.
  • Have you thought about string type? It’s often not enough to ensure that a string variable is not null. We want to work with not empty (or even not whitespace only) strings.
    The same thing applies to collections. It’s not enough that it’s not null. It has to have at least one element in order to compute an average (or to get the biggest element).

To address these issues we can transform the IfNotNull extension into the following IfNotDefault extension:

public static TResult IfNotDefault<TResult, TSource>(
    this TSource source,
    Func<TSource, TResult> onNotDefault,
    Predicate<TSource> isNotDefault = null)
{
    if (onNotDefault == null) throw new ArgumentNullException("onNotDefault");

    var isDefault = isNotDefault == null
        ? EqualityComparer<TSource>.Default.Equals(source, default(TSource))
        : !isNotDefault(source);

   return isDefault ? default(TResult) : onNotDefault(source);
}

It’s not much different from the original implementation. I’ve got rid of the where TSource : class constraint to support structs. After doing that I could no longer use simple null equality comparison (because structs aren’t nullable) so I had to ask EqualityComparer<TSource>.Default to do the job.
And there is also optional predicate, if we’re not happy with the default comparison.

Let me show you a couple of usage examples:

1. Performing some operation on string if it’s not empty:

return person
        . IfNotDefault(x => x.Name)
        . IfNotDefault(SomeOperation, x => !string.IsNullOrEmpty(x));

 

2. Computing average. This nicely works as a part of LINQ chain:

var avg = students
        .Where(IsNotAGraduate)
        .FirstOrDefault()
        .IfNotDefault(s => s.Grades) // let’s assume that Grades property returns int array 
        .IfNotDefault(g => g.Average(), g => g != null && g.Length > 0);

Notice one thing. The Average method returns double. If it’s not been possible to compute the average, then the avg variable will be set to 0. Sometimes it’s a desired side effect, other times it’s not.
If it is the latter, we can fix this easily with nullable cast:

        …
        .IfNotDefault(g => (double?)g.Average(), g => g != null && g.Length > 0);

 

3. Value types

Sometimes we can take advantage of the default struct values.
For example, the default value of double (and other numeric types) is 0.
We can use that fact to implement a division method:

public static double? Div(double dividend, double divisor)
{
    return divisor.IfNotDefault(_ => (double?)dividend / divisor);
}

The same thing is possible with booleans. Instead of writing:

return TrueCondition
    ? ComputeSomething()
    : null; 

we can write:

return TrueCondition.IfNotDefault(_ => ComputeSomething());

It’s up to you to decide if it’s overcomplicated or not. For me it’s just a useful tool that suits my needs in some cases.

 

Better future

There are good odds that in the future the IfNotNull extension will no longer be needed.
It may be replaced by a new Safe Navigation Operator. It’s supposed to be working in the following way:

//member access
obj?.Property; //instead of obj.IfNotNull(x => x.Property);

//method invocation
obj?.Method(); //instead of obj.IfNotNull(x => x.Method());

//indexing
obj?[0]; //instead of obj.IfNotNull(x => x[0]);

You can read more about it on the Visual Studio User Voice, where Mads Torgersen (the C# Language PM) said: “We are seriously considering this feature for C# and VB, and will be prototyping it in the coming months.”

 

Regards,
Jakub Niemyjski