6

When I get deserialized XML result into xsd-generated tree of objects and want to use some deep object inside that tree a.b.c.d.e.f, it will give me exception if any node on that query path is missing.

if(a.b.c.d.e.f != null)
    Console.Write("ok");

I want to avoid checking for null for each level like this:

if(a != null)
if(a.b != null)
if(a.b.c != null)
if(a.b.c.d != null)
if(a.b.c.d.e != null)
if(a.b.c.d.e.f != null)
    Console.Write("ok");

First solution is to implement Get extension method that allows this:

if(a.Get(o=>o.b).Get(o=>o.c).Get(o=>o.d).Get(o=>o.e).Get(o=>o.f) != null)
    Console.Write("ok");

Second solution is to implement Get(string) extension method and use reflection to get result looking like this:

if(a.Get("b.c.d.e.f") != null)
    Console.Write("ok");

Third solution, could be to implement ExpandoObject and use dynamic type to get result looking like this:

dynamic da = new SafeExpando(a);
if(da.b.c.d.e.f != null)
    Console.Write("ok");

But last 2 solutions do not give benefits of strong typing and IntelliSense.

I think the best could be fourth solution that can be implemented with Expression Trees:

if(Get(a.b.c.d.e.f) != null)
    Console.Write("ok");

or

if(a.Get(a=>a.b.c.d.e.f) != null)
    Console.Write("ok");

I already implemented 1st and 2nd solutions.

Here is how 1st solution looks like:

[DebuggerStepThrough]
public static To Get<From,To>(this From @this, Func<From,To> get)
{
    var ret = default(To);
    if(@this != null && !@this.Equals(default(From)))
        ret = get(@this);

    if(ret == null && typeof(To).IsArray)
        ret = (To)Activator.CreateInstance(typeof(To), 0);

    return ret;
}

How to implement 4th solution if possible ?

Also it would be interesting to see how to implement 3rd solution if possible.

alpav
  • 2,972
  • 3
  • 37
  • 47
  • [This question](http://stackoverflow.com/questions/3897249/how-to-avoid-multiple-if-null-checks) has two links to questions with answers. One depends on the Roslyn compiler; the other is a simple snippet of code that will do the trick. – D. Ben Knoble May 27 '15 at 16:27
  • I read the actual question and the answers above don't directly address your question but are relevant and interesting – D. Ben Knoble May 27 '15 at 16:28
  • I've been experimenting with this recently and maybe my solution would be interesing to you. I posted it [here](http://codereview.stackexchange.com/questions/116798/improved-nullguard-v3-that-supports-property-chains-methods-and-ignores-value-t) on Code Review. I wrote my own implementation because I wanted something more that just simple checking against null ;-) Your question and Servy's answer inspired me to try something different. – t3chb0t Jan 15 '16 at 21:46

1 Answers1

13

So the starting place is creating an expression visitor. This lets us find all of the member accesses within a particular expression. This leaves us with the question of what to do for each member access.

So the first thing is to recursively visit on the expression that the member is being accessed on. From there, we can use Expression.Condition to create a conditional block that compares that processed underlying expression to null, and returns null if true an the original starting expression if it's not.

Note that we need to provide implementations for both Members and method calls, but the process for each is basically identical.

We'll also add in a check so that of the underlying expression is null (which is to say, there is no instance and it's a static member) or if it's a non-nullable type, that we just use the base behavior instead.

public class MemberNullPropogationVisitor : ExpressionVisitor
{
    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Expression == null || !IsNullable(node.Expression.Type))
            return base.VisitMember(node);

        var expression = base.Visit(node.Expression);
        var nullBaseExpression = Expression.Constant(null, expression.Type);
        var test = Expression.Equal(expression, nullBaseExpression);
        var memberAccess = Expression.MakeMemberAccess(expression, node.Member);
        var nullMemberExpression = Expression.Constant(null, node.Type);
        return Expression.Condition(test, nullMemberExpression, node);
    }

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        if (node.Object == null || !IsNullable(node.Object.Type))
            return base.VisitMethodCall(node);

        var expression = base.Visit(node.Object);
        var nullBaseExpression = Expression.Constant(null, expression.Type);
        var test = Expression.Equal(expression, nullBaseExpression);
        var memberAccess = Expression.Call(expression, node.Method);
        var nullMemberExpression = Expression.Constant(null, MakeNullable(node.Type));
        return Expression.Condition(test, nullMemberExpression, node);
    }

    private static Type MakeNullable(Type type)
    {
        if (IsNullable(type))
            return type;

        return typeof(Nullable<>).MakeGenericType(type);
    }

    private static bool IsNullable(Type type)
    {
        if (type.IsClass)
            return true;
        return type.IsGenericType &&
            type.GetGenericTypeDefinition() == typeof(Nullable<>);
    }
}

We can then create an extension method to make calling it easier:

public static Expression PropogateNull(this Expression expression)
{
    return new MemberNullPropogationVisitor().Visit(expression);
}

As well as one that accepts a lambda, rather than any expression, and can return a compiled delegate:

public static Func<T> PropogateNull<T>(this Expression<Func<T>> expression)
{
    var defaultValue = Expression.Constant(default(T));
    var body = expression.Body.PropogateNull();
    if (body.Type != typeof(T))
        body = Expression.Coalesce(body, defaultValue);
    return Expression.Lambda<Func<T>>(body, expression.Parameters)
        .Compile();
}

Note that, to support cases where the accessed member resolves to a non-nullable value, we're changing the type of those expressions to lift them to be nullable, using MakeNullable. This is a problem with this final expression, as it needs to be a Func<T>, and it won't match if T isn't also lifted. Thus, while it's very much non-ideal (ideally you'd never call this method with a non-nullable T, but there's no good way to support this in C#) we coalesce the final value using the default value for that type, if necessary.

(You can trivially modify this to accept a lambda accepting a parameter, and pass in a value, but you can just as easily close over that parameter instead, so I see no real reason to.)


It's also worth pointing out that in C# 6.0, when it's actually released, we'll have an actual null propogation operator (?.), making all of this very unnecessary. You'll be able to write:

if(a?.b?.c?.d?.e?.f != null)
    Console.Write("ok");

and have exactly the semantics you're looking for.

Servy
  • 202,030
  • 26
  • 332
  • 449
  • Expression.Constant(null, node.Type) for value type raise exception, replace null with default value of type(http://stackoverflow.com/a/2490274/440030) resolved problem. – Reza ArabQaeni May 27 '15 at 18:16
  • @RezaArabQaeni That's not really the proper semantics for a null propagation operator. It should lift the result to a nullable type, but the limitations of the language don't really allow that to happen in all cases, only certain ones could be supported. Specifically, if the final expression gets lifted then it'll no longer match the signature of the delegate, and changing that is really beyond what C# can support effectively. Using the default value in that one case is at least better than always using it, I guess, so I've done that for now. – Servy May 27 '15 at 18:27
  • Ok, you right, So what's your idea about when node type is value type then instead of Expression.Condition (to check is property is null) return a simple Expression without check null condition, albeit i don't know what is implementaion of this simple expression. – Reza ArabQaeni May 27 '15 at 19:04
  • @RezaArabQaeni You could do that, yes. It'd be done the exact same way that I eladed the check if the expression that the member is invoked on isn't nullable. There's just a check for "is this type nullable, if not, stop here". I do the check just on the expression the member/method is invoked on. You could trivially add a check for the expression itself. You'd need to make the method calling it support only nullable types though, which is the hard part. – Servy May 27 '15 at 20:09