30

Say you are trying to read this property

var town = Staff.HomeAddress.Postcode.Town;

Somewhere along the chain a null could exist. What would be the best way of reading Town?

I have been experimenting with a couple of extension methods...

public static T2 IfNotNull<T1, T2>(this T1 t, Func<T1, T2> fn) where T1 : class
{
    return t != null ? fn(t) : default(T2);
}

var town = staff.HomeAddress.IfNotNull(x => x.Postcode.IfNotNull(y=> y.Town));

or

public static T2 TryGet<T1, T2>(this T1 t, Func<T1, T2> fn) where T1 : class
{
if (t != null)
{
    try
    {
        return fn(t);
    }
    catch{ }
}
return default(T2);
}

var town = staff.TryGet(x=> x.HomeAddress.Postcode.Town);

Obviously these are just abstracting away the logic and making the code (a little) more readable.

But is there a better/ more efficient way?

EDIT:

In my particular case the objects are being returned from a WCF service and I have no control over the architecture of those objects.

EDIT 2:

There is also this method:

public static class Nullify
{
    public static TR Get<TF, TR>(TF t, Func<TF, TR> f) where TF : class
    {
        return t != null ? f(t) : default(TR);
    }

    public static TR Get<T1, T2, TR>(T1 p1, Func<T1, T2> p2, Func<T2, TR> p3)
        where T1 : class
        where T2 : class
    {
        return Get(Get(p1, p2), p3);
    }

    /// <summary>
    /// Simplifies null checking as for the pseudocode
    ///     var r = Pharmacy?.GuildMembership?.State?.Name
    /// can be written as
    ///     var r = Nullify( Pharmacy, p => p.GuildMembership, g => g.State, s => s.Name );
    /// </summary>
    public static TR Get<T1, T2, T3, TR>(T1 p1, Func<T1, T2> p2, Func<T2, T3> p3, Func<T3, TR> p4)
        where T1 : class
        where T2 : class
        where T3 : class
    {
        return Get(Get(Get(p1, p2), p3), p4);
    }
}

from this article http://qualityofdata.com/2011/01/27/nullsafe-dereference-operator-in-c/

David
  • 8,340
  • 7
  • 49
  • 71
  • I rather like your IfNotNull method. – Justin Morgan - On strike Apr 21 '11 at 13:27
  • 2
    Here's a nice blog post about this - [Nullsafe dereference operator (?.) in C#](http://qualityofdata.com/2011/01/27/nullsafe-dereference-operator-in-c/). It has also been suggested on Connect [here](https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=192177) and [here](https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=432830). – Danko Durbić Apr 21 '11 at 15:15
  • @Danko: thats a great article! – David Apr 21 '11 at 15:36
  • This is a similar question (that was posted after Dve's question): http://stackoverflow.com/questions/17672481/cleaner-way-to-do-a-null-check-in-c/17683027#17683027 – Kevin Jul 17 '13 at 22:42

11 Answers11

19

The best way would be to avoid violating the law of Demeter.

var town = Staff.GetTown();

And in Staff:

string GetTown()
{
    HomeAddress.GetTown();
}

And in HomeAddress:

string GetTown()
{
    PostCode.GetTown();
}

And in PostCode:

string GetTown()
{
    Town.GetTownName();
}

Update:

Since you don't have control over this, you can use short circuit evaluation:

if(Staff != null 
   && Staff.HomeAddress != null
   && Staff.HomeAddress.PostCode != null
   && Staff.HomeAddress.PostCode.Town != null)
{
    var town = Staff.HomeAddress.Postcode.Town;
}
Oded
  • 489,969
  • 99
  • 883
  • 1,009
10

I agree with Oded that this violates the Law of Demeter.

I was intrigued by your question though, so I wrote up a a poor man's "Null-Safe Evaluate" extension-method with expression-trees, just for fun. This should give you compact syntax to express the desired semantics.

Please don't use this in production code.

Usage:

var town = Staff.NullSafeEvaluate(s => s.HomeAddress.Postcode.Town);

This will evaluate in succession:

Staff
Staff.HomeAddress
Staff.HomeAddress.Postcode
Staff.HomeAddress.Postcode.Town

(Caching and reusing the values of the intermediate expressions to produce the next one)

If it encounters a null reference, it returns the default value of the type of Town. Otherwise, it returns the value of the full expression.

(Not throughly tested, can be improved in terms of performance and doesn't support instance-methods. POC only.)

public static TOutput NullSafeEvaluate<TInput, TOutput>
        (this TInput input, Expression<Func<TInput, TOutput>> selector)
{
    if (selector == null)
        throw new ArgumentNullException("selector");

    if (input == null)
        return default(TOutput);

    return EvaluateIterativelyOrDefault<TOutput>
            (input, GetSubExpressions(selector));
}

private static T EvaluateIterativelyOrDefault<T>
        (object rootObject, IEnumerable<MemberExpression> expressions)
{
    object currentObject = rootObject;

    foreach (var sourceMemEx in expressions)
    {
        // Produce next "nested" member-expression. 
        // Reuse the value of the last expression rather than 
        // re-evaluating from scratch.
        var currentEx = Expression.MakeMemberAccess
                      (Expression.Constant(currentObject), sourceMemEx.Member);


        // Evaluate expression.
        var method = Expression.Lambda(currentEx).Compile();
        currentObject = method.DynamicInvoke();

        // Expression evaluates to null, return default.
        if (currentObject == null)
            return default(T);
    }

    // All ok.
    return (T)currentObject;
}

private static IEnumerable<MemberExpression> GetSubExpressions<TInput, TOutput>
        (Expression<Func<TInput, TOutput>> selector)
{
    var stack = new Stack<MemberExpression>();

    var parameter = selector.Parameters.Single();
    var currentSubEx = selector.Body;

    // Iterate through the nested expressions, "reversing" their order.
    // Stop when we reach the "root", which must be the sole parameter.
    while (currentSubEx != parameter)
    {
        var memEx = currentSubEx as MemberExpression;

        if (memEx != null)
        {
            // Valid member-expression, push. 
            stack.Push(memEx);
            currentSubEx = memEx.Expression;
        }

        // It isn't a member-expression, it must be the parameter.
        else if (currentSubEx != parameter)
        {

            // No, it isn't. Throw, don't support arbitrary expressions.
            throw new ArgumentException
                        ("Expression not of the expected form.", "selector");
        }
    }

    return stack;
}
Ani
  • 111,048
  • 26
  • 262
  • 307
  • I have something similar, but the trick where you reduced known values to constants was helpful. Thanks. – Casey Mar 13 '14 at 16:10
9
    var town = "DefaultCity";
    if (Staff != null &&
        Staff.HomeAddress != null &&
        Staff.HomeAddress.Postcode != null &&
        Staff.HomeAddress.Postcode.Town != null)
    {
        town = Staff.HomeAddress.Postcode.Town;
    }
Evgeny Gavrin
  • 7,627
  • 1
  • 22
  • 27
  • 1
    Is this more efficient than doing the TryGet method though? – David Apr 21 '11 at 13:31
  • 9
    This might not win you any points for elegance/creativity in a comp sci classroom. But if I came across this code "in the wild", I would immediately know what it was doing and why. Sometimes simplicity/pragmatism is the best approach. – mikemanne Apr 21 '11 at 13:33
  • Yep. Try/gets are more consuming in this case – Evgeny Gavrin Apr 21 '11 at 13:34
  • @mikemanne: I agree it is instantly understandable. but is staff.TryGet(x=> x.HomeAddress.Postcode.Town) that much worse? – David Apr 21 '11 at 13:57
  • 3
    @Dve - it _is_ much worse, as you are relying on an exception to be thrown (and if it gets thrown, it _will_ be more expensive). – Oded Apr 21 '11 at 14:14
1

@Oded's and others' answers still hold true in 2016 but c# 6 introduced the null-conditional operator which provides the elegance you are after.

using System;

public class Program
{
    public class C {
        public C ( string town ) {Town = town;}
        public string Town { get; private set;}
    }
    public class B {
        public B( C c ) {C = c; }
        public C C {get; private set; }
    }
    public class A {
        public A( B b ) {B = b; }
        public B B {get; private set; }
    }
    public static void Main()
    {
        var a = new A(null);
        Console.WriteLine( a?.B?.C?.Town ?? "Town is null.");
    }
}
tymtam
  • 31,798
  • 8
  • 86
  • 126
1

As per encapsulation, it is always the duty of a class to make proper validation (i.e. null-checks) for it's fields (and properties) before returning them. So each object is responsible for its fields, you can choose to return null, empty string, or raise an exception and handle it one level up in the chain. Trying to work around this is like trying to work around encapsulation.

Teoman Soygul
  • 25,584
  • 6
  • 69
  • 80
1

Here a solution using null coalescing operators that I put together for fun (other answers are better). If you except this as the answer I'll have to hunt you down and uh, take your keyboard away! :-)

Basically, if any object in Staff is null its default will be used instead.

// define a defaultModel
var defaultModel = new { HomeAddress = new { PostCode = new { Town = "Default Town" } } };
// null coalesce through the chain setting defaults along the way.
var town = (((Staff ?? defaultModel)
                .HomeAddress  ?? defaultModel.HomeAddress)
                    .PostCode ?? defaultModel.HomeAddress.PostCode)
                        .Town ?? defaultModel.HomeAddress.PostCode.Town;

Disclaimer, I'm a javascript guy and we javascripters know that accessing an object's properties can get expensive - so we tend to cache just above everything, which is what the code above accomplishes (each property is only looked up once). With C#'s compilers and optimizers it probably isn't necessary to do it (some confirmation on this would be nice).

David Murdoch
  • 87,823
  • 39
  • 148
  • 191
1

I came up with the same solution as Ani's some time ago, see this blog post for details. While elegant, it's very inefficient...

var town = Staff.NullSafeEval(s => s.HomeAddress.Postcode.Town, "(N/A)");

A better solution IMHO is the one suggested in this CodeProject article:

string town = Staff.With(s => s.HomeAddress)
                   .With(a => a.Postcode)
                   .With(p => p.Town);

The only thing I don't like with this solution is the name of the extension method, but it can easily be changed...

Community
  • 1
  • 1
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
0

You could also consider using the Maybe monad and having an extension method like ToMaybe() that gives you a Just a if the object is not null, a Nothing if it is.

I won't go into the implementation details (unless someone asks) but the code would look like this:

var maybeTown = from s in staff.ToMaybe()
                from h in s.HomeAddress.ToMaybe()
                from p in h.Postcode.ToMaybe()
                from t in p.Town.ToMaybe()
                select t;
var town = maybeTown.OrElse(null);

which is really clean or really ugly depending on you point of view

miniBill
  • 1,743
  • 17
  • 41
0

How often do you expect a null? If (and only if) it will be infrequent, I would use

try
{
    var town = staff.HomeAddress.Postcode.Town;
    // stuff to do if we could get the town
}
catch (NullReferenceException)
{
    // stuff to do if there is a null along the way
}
Allison Lock
  • 2,375
  • 15
  • 17
0

Another go:

Declare a helper method

bool HasNull(params object[] objects)
{
    foreach (object o in objects) { if (o == null) return true; }
    return false;
}

Then use it like this:

if (!HasNull(Staff, Staff.HomeAdress, Staff.HomeAddress.Postcode, Staff.HomeAddress.Postcode.Town))
{
    town = Staff.HomeAddress.Postcode.Town;
}
Peter
  • 1,595
  • 13
  • 18
-1

Can't test right now, but wouldn't something like this work?

if (Staff??Staff.HomeAdress??Staff.HomeAddress.Postcode??Staff.HomeAddress.Postcode.Town != null)
{
    var town = Staff.HomeAddress.Postcode.Town
}
Peter
  • 1,595
  • 13
  • 18
  • 1
    `Staff ?? Staff.HomeAddress` has different types on both sides of the operator, so this usage won't compile. Additionally, if `Staff` is null, the `??` operator will invoke `Staff.HomeAddress` resulting in a null ref exception. – Juliet Apr 21 '11 at 21:28
  • Posted very late yesterday (it was, here in Thailand), and realized all the shortcomings later. So, no, it wouldn't work... – Peter Apr 22 '11 at 07:48