How do I break down a chain of member access expressions?

c# expression-trees lambda lifting nullreferenceexception

Question

The Quick Take (TL;DR)

Let's say that my expression is nothing more than a chain of member access operators:

Expression<Func<Tx, Tbaz>> e = x => x.foo.bar.baz;

This expression may be thought of as a collection of smaller expressions, each of which contains a single member-access operation:

Expression<Func<Tx, Tfoo>>   e1 = (Tx x) => x.foo;
Expression<Func<Tfoo, Tbar>> e2 = (Tfoo foo) => foo.bar;
Expression<Func<Tbar, Tbaz>> e3 = (Tbar bar) => bar.baz;

Breaking is what I want to do.e broken into these different sub-expressions, allowing me to deal with each one separately.

The Shorter Version Is...

If I may use the phrasex => x.foo.bar I'm already adept at breaking offx => x.foo How can I extract the second sub-expression?foo => foo.bar ?

Reasons for my actions:

As in Existential access operator in CoffeeScript?., I'm attempting to imitate "lifting" the member access operator in C#. Although Eric Lippert claimed that For C#, a comparable operator was proposed,, there was no funding available to carry it out.

If C# had such an operator, you could do the following:

value = target?.foo?.bar?.baz;

If even just onetarget.foo.bar.baz This whole thing would evaluate to null, preventing a NullReferenceException, if chain turned out to be null.

I need aLift a technique for extension that can replicate this

value = target.Lift(x => x.foo.bar.baz); //returns target.foo.bar.baz or null

Attempts I've Made:

I have a program that builds and sort of functions. I only know how to maintain the left side of a member access expression, thus it isn't complete. I can pivotx => x.foo.bar.baz into x => x.foo.bar , but I have no idea how to maintainbar => bar.baz .

In the end, it does the following (pseudocode):

return (x => x)(target) == null ? null
       : (x => x.foo)(target) == null ? null
       : (x => x.foo.bar)(target) == null ? null
       : (x => x.foo.bar.baz)(target);

This indicates that the expression's leftmost stages are continually examined. If they're simply attributes on POCO objects, they may not be a major concern, but when they're method calls, the inefficiencies (and possible side consequences) become much more obvious:

//still pseudocode
return (x => x())(target) == null ? null
       : (x => x().foo())(target) == null ? null
       : (x => x().foo().bar())(target) == null ? null
       : (x => x().foo().bar().baz())(target);

The Law:

static TResult Lift<T, TResult>(this T target, Expression<Func<T, TResult>> exp)
    where TResult : class
{
    //omitted: if target can be null && target == null, just return null

    var memberExpression = exp.Body as MemberExpression;
    if (memberExpression != null)
    {
        //if memberExpression is {x.foo.bar}, then innerExpression is {x.foo}
        var innerExpression = memberExpression.Expression;
        var innerLambda = Expression.Lambda<Func<T, object>>(
                              innerExpression, 
                              exp.Parameters
                          );  

        if (target.Lift(innerLambda) == null)
        {
            return null;
        }
        else
        {
            ////This is the part I'm stuck on. Possible pseudocode:
            //var member = memberExpression.Member;              
            //return GetValueOfMember(target.Lift(innerLambda), member);
        }
    }

    //For now, I'm stuck with this:
    return exp.Compile()(target);
}

Zzz-119-Zzz served as a vague inspiration for this.


Lift Method Alternatives and Why I Can't Use Them

The monad Maybe

value = x.ToMaybe()
         .Bind(y => y.foo)
         .Bind(f => f.bar)
         .Bind(b => b.baz)
         .Value;
Pros:
  1. utilizes a well-liked pattern in functional programming
  2. In addition to elevated member access, additional applications
Cons:
  1. It's excessively wordy. Every time I want to dig down on a few people, I don't want to have to call a huge number of functions. even though I useSelectMany and utilize the query syntax; in my opinion, this will make things appear messier, not better.
  2. I have to rewrite it by hand.x.foo.bar.baz as its constituent parts, thus I have to be aware of them throughout compilation. I am unable to utilize a variable's expression directly, like asresult = Lift(expr, obj); .
  3. It doesn't seem like it was made just for what I'm trying to achieve and isn't really a great match.

ExpressionVisitor

Zzz-185-Zzz was changed by me into a general extension approach that may be used as I've explained. The code is too lengthy to present here, but if anybody is interested, I'll release a Gist.

Pros:
  1. adheres toresult = target.Lift(x => x.foo.bar.baz) syntax
  2. If each link in the chain returns a reference type or a non-nullable value type, it works well.
Cons:
  1. Its usefulness to me is severely limited by the fact that it chokes if any member of the chain is a nullable value type. It must function for me.Nullable<DateTime> members.

Try/catch

try 
{ 
    value = x.foo.bar.baz; 
}
catch (NullReferenceException ex) 
{ 
    value = null; 
}

If I am unable to discover a more elegant solution, I will choose this method since it is the most apparent.

Pros:
  1. It's easy.
  2. The purpose of the code is clear.
  3. I'm not concerned about edge cases.
Cons:
  1. It is verbose and unsightly.
  2. The performance cost of the try/catch block is nontrivial*.
  3. I am unable to make it produce an expression tree for LINQ since it is a statement block.
  4. It like a declaration of defeat.

I won't lie; the major reason I'm being so obstinate is "not accepting defeat." My intuition tells me there must be a classy method to do this, but I've had trouble finding one. I find it incredible that the left half of an expression is so accessible, yet the right side is almost inaccessible.

Since I really have two issues here, I'll welcome any solution that addresses any of them:

  • Expression decomposition that protects both sides, performs well, and is applicable to any type
  • granting members access to nulls

Update:

Access for members propagating nulls is is C# 6.0 intended forcomprised in. However, I'd still prefer an answer to the expression decomposition problem.

1
22
5/23/2017 12:16:59 PM

Accepted Answer

There is a straightforward fix if it's simply a short sequence of member access expressions:

public static TResult Lift<T, TResult>(this T target, Expression<Func<T, TResult>> exp)
    where TResult : class
{
    return (TResult) GetValueOfExpression(target, exp.Body);
}

private static object GetValueOfExpression<T>(T target, Expression exp)
{
    if (exp.NodeType == ExpressionType.Parameter)
    {
        return target;
    }
    else if (exp.NodeType == ExpressionType.MemberAccess)
    {
        var memberExpression = (MemberExpression) exp;
        var parentValue = GetValueOfExpression(target, memberExpression.Expression);

        if (parentValue == null)
        {
            return null;
        }
        else
        {
            if (memberExpression.Member is PropertyInfo)
                return ((PropertyInfo) memberExpression.Member).GetValue(parentValue, null);
            else
                return ((FieldInfo) memberExpression.Member).GetValue(parentValue);
        }
    }
    else
    {
        throw new ArgumentException("The expression must contain only member access calls.", "exp");
    }
}

EDIT

Use this more recent technique to add support for method calls:

private static object GetValueOfExpression<T>(T target, Expression exp)
{
    if (exp == null)
    {
        return null;
    }
    else if (exp.NodeType == ExpressionType.Parameter)
    {
        return target;
    }
    else if (exp.NodeType == ExpressionType.Constant)
    {
        return ((ConstantExpression) exp).Value;
    }
    else if (exp.NodeType == ExpressionType.Lambda)
    {
        return exp;
    }
    else if (exp.NodeType == ExpressionType.MemberAccess)
    {
        var memberExpression = (MemberExpression) exp;
        var parentValue = GetValueOfExpression(target, memberExpression.Expression);

        if (parentValue == null)
        {
            return null;
        }
        else
        {
            if (memberExpression.Member is PropertyInfo)
                return ((PropertyInfo) memberExpression.Member).GetValue(parentValue, null);
            else
                return ((FieldInfo) memberExpression.Member).GetValue(parentValue);
        }
    }
    else if (exp.NodeType == ExpressionType.Call)
    {
        var methodCallExpression = (MethodCallExpression) exp;
        var parentValue = GetValueOfExpression(target, methodCallExpression.Object);

        if (parentValue == null && !methodCallExpression.Method.IsStatic)
        {
            return null;
        }
        else
        {
            var arguments = methodCallExpression.Arguments.Select(a => GetValueOfExpression(target, a)).ToArray();

            // Required for comverting expression parameters to delegate calls
            var parameters = methodCallExpression.Method.GetParameters();
            for (int i = 0; i < parameters.Length; i++)
            {
                if (typeof(Delegate).IsAssignableFrom(parameters[i].ParameterType))
                {
                    arguments[i] = ((LambdaExpression) arguments[i]).Compile();
                }
            }

            if (arguments.Length > 0 && arguments[0] == null && methodCallExpression.Method.IsStatic &&
                methodCallExpression.Method.IsDefined(typeof(ExtensionAttribute), false)) // extension method
            {
                return null;
            }
            else
            {
                return methodCallExpression.Method.Invoke(parentValue, arguments);
            }
        }
    }
    else
    {
        throw new ArgumentException(
            string.Format("Expression type '{0}' is invalid for member invoking.", exp.NodeType));
    }
}
8
6/22/2012 2:33:44 PM


Related Questions





Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow