Search code examples
c#expression-trees

Null check on aggregate expression


I have an expression helper to help getting values from object hierarchy

   public static Func<T, object> GetMemberExpressionFunc<T>(string expressionString)
   {
     if (string.IsNullOrEmpty(expressionString))
     {
         throw new InvalidOperationException("invalid Expression");
     }

     var parameter = Expression.Parameter(typeof(T));
     Expression memberExpression = parameter;
     var tokens = expressionString.Split('.');
     memberExpression = tokens.Aggregate(memberExpression, Expression.PropertyOrField);

     var convertExpression = Expression.Convert(memberExpression, typeof(object));
     return Expression.Lambda<Func<T, object>>(convertExpression, parameter)
                        .Compile();

    }

Usage

public class A
{
        public B BObj { get; set; }
}

public class B
{
        public string Name { get; set; }

}

 static void Main(string[] args)
 {
   var obj = new A {BObj = new B {Name ="Test"}};
   var obj2 = new A ();

   var exp = ExpressionHelper.GetMemberExpressionFunc<A>("BObj.Name");

  var test1 =  exp(obj);// test1 is "Test"

  var test2 = exp(obj2); //throws because BObj is null
}

I want the expression to return null if any property in the hierarchy is null instead of throwing exception. Is it possible to do this at the aggregate expression?


Solution

  • C# null conditional operator ?. would have been very handy in this case. Unfortunately it's still not supported in expression trees, so one way to achieve the goal is to build dynamically the equivalent of manual null checks all down the road:

    x => x != null && x.Prop1 != null && x.Prop1.Prop2 != null ... ? (object)x.Prop1.Prop2...PropN : null
    

    Since you would need to aggregate both the member accessor expression and condition to be used in the final Expression.Condition, the Aggregate method is not good - doing the aggregation with the old good foreach loop looks more appropriate, for instance like this:

    var parameter = Expression.Parameter(typeof(T));
    
    var nullConst = Expression.Constant(null);
    Expression source = parameter, condition = null;
    foreach (var memberName in expressionString.Split('.'))
    {
        var notNull = Expression.NotEqual(source, nullConst);
        condition = condition != null ? Expression.AndAlso(condition, notNull) : notNull;
        source = Expression.PropertyOrField(source, memberName);
    }
    source = Expression.Convert(source, typeof(object));
    var body = Expression.Condition(condition, source, nullConst);
    
    return Expression.Lambda<Func<T, object>>(body, parameter)
                       .Compile();