Click here to Skip to main content
15,436,765 members
Articles / Programming Languages / C# 4.0
Posted 11 Sep 2010


160 bookmarked

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
Shows how a few Extension Methods solve the 'repeated null check' problem.


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.

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.

string postCode;
if (person != null)
  if (HasMedicalRecord(person) && person.Address != null)
    if (person.Address.PostCode != null)
      postCode = person.Address.PostCode.ToString();
      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.


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:

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:

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.


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.

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:

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:

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;


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.

public static TInput Do<TInput>(this TInput o, Action<TInput> action) 
       where TInput: class
  if (o == null) return null;
  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:

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!


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:

public override void VisitInvocationExpression(IInvocationExpression 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(

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:

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:

public override void VisitInvocationExpression(IInvocationExpression 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:

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


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

QuestionShould return trigger evaluation of failure value when the source is not null? Pin
Shilohe24-Oct-17 8:47
MemberShilohe24-Oct-17 8:47 
GeneralMy vote of 5 Pin
dibley197328-Dec-15 19:10
Memberdibley197328-Dec-15 19:10 
QuestionHow would you handle parameter validation? Pin
flodpanter5-Mar-15 0:25
Memberflodpanter5-Mar-15 0:25 
QuestionVote of 5 Pin
Sander Rossel10-Feb-13 0:10
professionalSander Rossel10-Feb-13 0:10 
GeneralMy vote of 5 Pin
BC @ CV11-Jan-13 10:58
MemberBC @ CV11-Jan-13 10:58 
QuestionI voted 5. Pin
Rob Lyndon1-Jan-13 6:42
MemberRob Lyndon1-Jan-13 6:42 
QuestionAlternative... Pin
Paulo Zemek13-Jun-12 5:38
MemberPaulo Zemek13-Jun-12 5:38 
QuestionSimilar ideas, but different function names Pin
Michael Freidgeim1-Jun-12 13:54
MemberMichael Freidgeim1-Jun-12 13:54 
QuestionSource? Pin
Jason Vogel18-Apr-12 18:21
MemberJason Vogel18-Apr-12 18:21 
AnswerRe: Source? Pin
Dmitri Nеstеruk18-Apr-12 19:24
MemberDmitri Nеstеruk18-Apr-12 19:24 
GeneralMy vote of 5 Pin
Stephen Nguyen29-Oct-11 7:42
MemberStephen Nguyen29-Oct-11 7:42 
QuestionGood One Pin
dineshRajan21-Sep-11 21:49
MemberdineshRajan21-Sep-11 21:49 
QuestionMy vote of 5 Pin
Filip D'haene31-Jul-11 4:26
MemberFilip D'haene31-Jul-11 4:26 
QuestionA sincere, "Thank you!" Pin
Mythias26-Jul-11 20:37
MemberMythias26-Jul-11 20:37 
GeneralMy vote of 5 Pin
DrABELL24-Apr-11 17:01
MemberDrABELL24-Apr-11 17:01 
GeneralHopefully a Improvement Pin
Muhammad Shoaib Raja28-Dec-10 6:12
MemberMuhammad Shoaib Raja28-Dec-10 6:12 
GeneralRe: Hopefully a Improvement Pin
Dmitri Nеstеruk28-Dec-10 9:13
MemberDmitri Nеstеruk28-Dec-10 9:13 
GeneralMy vote of 5 Pin
Muhammad Shoaib Raja23-Nov-10 4:01
MemberMuhammad Shoaib Raja23-Nov-10 4:01 
GeneralMy vote of 5 Pin
Solid State Coder20-Oct-10 6:07
professionalSolid State Coder20-Oct-10 6:07 
GeneralMy vote of 5 Pin
Vercas18-Oct-10 23:01
MemberVercas18-Oct-10 23:01 
GeneralMy vote of 5 Pin
Vinod Kumar Gupta18-Oct-10 0:06
MemberVinod Kumar Gupta18-Oct-10 0:06 
GeneralMy vote of 4 Pin
Bigdeak11-Oct-10 22:32
MemberBigdeak11-Oct-10 22:32 
GeneralMy vote of 5 Pin
sashidhar10-Oct-10 23:33
Membersashidhar10-Oct-10 23:33 
GeneralMy vote of 5 Pin
beatxym10-Oct-10 22:03
Memberbeatxym10-Oct-10 22:03 
GeneralVote 4. Creative (and awesome) but... Pin
Simon Dufour8-Oct-10 7:59
MemberSimon Dufour8-Oct-10 7:59 

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.