Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C# 4.0

Chained null checks and the Maybe monad

Rate me:
Please Sign up or sign in to vote.
4.94/5 (145 votes)
21 Sep 2010CPOL5 min read 221.4K   160   75
Shows how a few Extension Methods solve the 'repeated null check' problem.

Introduction

A great many programmers have met a situation where, while accessing a nested object property (e.g., person.Address.PostCode), they have to do several null checks. This requirement frequently pops up in XML parsing where missing elements and attributes can return null when you attempt to access them (and subsequently trying to access Value throws a NullReferenceException). In this article, I'll show how a take on the Maybe monad in C#, coupled with the use of Extension Methods, can be used to improve readability.

Problem Description

So, to start with, let's look at the way to get a person's post code (just imagine you're working with XML or something). The code shown below does several null checks, and assigns the value only if it is available.

C#
string postCode = null;
if (person != null && person.Address != null && 
    person.Address.PostCode != null)
{
  postCode = person.Address.PostCode.ToString();
}

What you've got up there is some fairly unreadable (and un-maintainable) code. Actually, we're lucky to have all of our code fall under a single if - something that might not be possible in a more complex scenario. Let's imagine a more complicated situation - say, we need to perform some operation between the if evaluations. What do we get? That's right - a chain of ifs.

C#
string postCode;
if (person != null)
{
  if (HasMedicalRecord(person) && person.Address != null)
  {
    CheckAddress(person.Address);
    if (person.Address.PostCode != null)
      postCode = person.Address.PostCode.ToString();
    else
      postCode = "UNKNOWN";
  }
}

The code presented above contains a lot of excess data - for example, person.Address.PostCode is mentioned twice. There's nothing incorrect about the code per se, it just has a bit too many symbols. To sum up, we want our code to communicate better that:

  • If the value is null, no further evaluations should be done; if the value is not null, then this is the value we're going to work with.
  • If we perform some action, it only happens on a valid object.

So what am I suggesting? I propose that we create a fluent interface that will satisfy the above conditions without any nesting. To do that, we are going to employ the Maybe monad.

For those of you who know F#, the Maybe monad will be familiar as the Option type. For C# developers, let's just assume that you can have a variable that either has some value or no value (none). Of course, C# doesn't directly support this none-some duality except by using null. Which is precisely why I'm proposing the chained extension solution presented below.

With

Our primary concern is to do the null checks to 'shorten' them so they don't pollute our code. For that, we'll define a With() extension method:

C#
public static TResult With<TInput, TResult>(this TInput o, 
       Func<TInput, TResult> evaluator)
       where TResult : class where TInput : class
{
  if (o == null) return null;
  return evaluator(o);
}

The above method can be attached to any type (because TInput is effectively object). As a parameter, this method takes a function which defines the next value in the chain. If we pass null, we get null back. Let's rewrite our first example using this method:

C#
string postCode = this.With(x => person)
                      .With(x => x.Address)
                      .With(x => x.PostCode);

I suppose, in the above example, we could replace Func<> with Expression<> and try to pull the properties, but I've seen this done, and the resulting code is too slow, and it's also somewhat limiting - it assumes that you're working with just one object, whereas my Maybe chains can (and do) drag in many objects.

Return

Here comes another piece of syntactic sugar - the Return() method. This method will return the 'current' value just like Where() does, but in case null was passed, it will return a different value that we supply. Consider this a kind of a «Where() with fallback» method.

C#
public static TResult Return<TInput,TResult>(this TInput o, 
       Func<TInput, TResult> evaluator, TResult failureValue) where TInput: class
{
  if (o == null) return failureValue;
  return evaluator(o);
}

So let's assume now that, with the absence of a postcode, we want to return, say, string.Empty. Here's how:

C#
string postCode = this.With(x => person).With(x => x.Address)
                      .Return(x => x.PostCode, string.Empty);

By the way, you could rewrite the Extension Method so that failureValue would also be computed via a Func<> - I am yet to meet a scenario where this is required, though. It is typically the case that we never know at which stage the chain failed (and yielded null), so the terminal Return() is typically an indicator (either true/false or null/not null).

If & Unless

Going through the call chain, you sometimes need to do checks not related to null. Theoretically, you could suspend the chain and use an if, or you could use an if in one of the delegates, but... you can simply define an If() extension method (and an Unless() if you feel like it) and plug it into the chain:

C#
public static TInput If<TInput>(this TInput o, Func<TInput, bool> evaluator) 
       where TInput : class
{
  if (o == null) return null;
  return evaluator(o) ? o : null;
}
 
public static TInput Unless<TInput>(this TInput o, Func<TInput, bool> evaluator)
       where TInput : class
{
  if (o == null) return null;
  return evaluator(o) ? null : o;
}

Do

Seeing how we're having a party here, let's add yet another method that simply calls a delegate - and that's it. Of course, this method is best used for one-line calls, and not for evaluating 20-line algorithms with convoluted logic. Nevertheless, the call is quite useful in practice.

C#
public static TInput Do<TInput>(this TInput o, Action<TInput> action) 
       where TInput: class
{
  if (o == null) return null;
  action(o);
  return o;
}

So, we're done: we've got the infrastructure we need to get our post code extraction to be a bit more readable. Here is the end result:

C#
string postCode = this.With(x => person)
    .If(x => HasMedicalRecord(x))
    .With(x => x.Address)
    .Do(x => CheckAddress(x))
    .With(x => x.PostCode)
    .Return(x => x.ToString(), "UNKNOWN");

As you can see, the depth of nesting has fallen to zero - no more curly braces!

Discussion

I use these Maybe-monadic-chain-null-extension-methods (call them how you will) in my R2P software product. Here's an example of a real-life use of these constructs:

C#
public override void VisitInvocationExpression(IInvocationExpression expression)
{
  base.VisitInvocationExpression(expression);
  string typeName = this.With(x => expression)
    .With(x => x.InvokedExpression)
    .With(x => x as IReferenceExpression)
    .With(x => x.Reference)
    .With(x => x.Resolve())
    .With(x => x.DeclaredElement)
    .With(x => x.GetContainingType())
    .Return(x => x.CLRName, null);
  this.If(x => Array.IndexOf(types, typeName) != -1)
    .With(x => ExpressionStatementNavigator.GetByExpression(expression))
    .Do(x =>
          {
            var suggestion = new SideEffectSuggestion(typeName);
            var highlightInfo = new HighlightingInfo(
              expression.GetDocumentRange(),
              suggestion);
            context.HighlightingInfos.Add(highlightInfo);
          });
}

I have to point out here that, at any point, you can stop the chain and start a new one. Why would you want that? Well, for example, you cannot define shared variables within the chain (unless you refactor it all to have a Dictionary<string,object>-like parameter).

By the way, quite frequently, I find myself making additional, domain-specific methods to plug into this chain. For example:

C#
public static IElement IsWithin<TContainingType>(this IElement self) 
       where TContainingType: class, IElement
{
  if (self == null) return self;
  var owner = self.GetContainingElement<TContainingType>(false);
  return owner == null ? self : null;
}

One more thing: this type of notation is actually light obfuscation because, as I'm sure you've guessed, each Extension Method call will be shown as a static method call in Reflector:

C#
public override void VisitInvocationExpression(IInvocationExpression expression)
{
    base.VisitInvocationExpression(expression);
    string typeName = this.With<SideEffectAnalyser, IInvocationExpression>(
    delegate (SideEffectAnalyser x) {
        return expression;
    }).With<IInvocationExpression, ICSharpExpression>(delegate (IInvocationExpression x) {
        return x.InvokedExpression;
    }).With<ICSharpExpression, IReferenceExpression>(delegate (ICSharpExpression x) {
        return (x as IReferenceExpression);
    }).With<IReferenceExpression, IReference>(delegate (IReferenceExpression x) {
        return x.Reference;
    })
    .
    .
    .
    // and so on
}

This approach is easily extensible - for example, a colleague of mine does try-catch checks in his chains, too. Hey, this is kind of like AOP, but without post-build or dynamic proxies. Oh, and the performance hit for these chains is negligible compared to if statements.

That's it! Comments are, as always, welcome!

Update 1: I got a question on how to propagate value types through this hierarchy. This is easy: all you have to do is create another chain method that doesn't do the null check:

C#
public static TResult WithValue<TInput, TResult>(this TInput o, 
       Func<TInput, TResult> evaluator)
       where TInput:struct
{
  return evaluator(o);
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder ActiveMesa
United Kingdom United Kingdom
I work primarily with the .NET technology stack, and specialize in accelerated code production via code generation (static or dynamic), aspect-oriented programming, MDA, domain-specific languages and anything else that gets products out the door faster. My languages of choice are C# and C++, though I'm open to suggestions.

Comments and Discussions

 
QuestionSource? Pin
Jason Vogel18-Apr-12 18:21
Jason Vogel18-Apr-12 18:21 
AnswerRe: Source? Pin
Dmitri Nеstеruk18-Apr-12 19:24
Dmitri Nеstеruk18-Apr-12 19:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.