Search code examples
c#lambdaexpression-trees

Null propagation in Expression Tree


See sample code below. How can I modify it to handle null values, similar to how the ?. operator works?

class Program
{
    static LambdaExpression GetExpression(Expression<Func<string, string>> expr)
    {
        return expr;
    }

    static void Main(string[] args)
    {
        // I want to perform the following null propagation check
        // in the expression tree below.
        // (s as string)?.Replace("a", "o");

        var expr = GetExpression(t => t);

        var oldValue = Expression.Constant("a", typeof(string));
        var newValue = Expression.Constant("o", typeof(string));
        var mi = typeof(string).GetMethod(nameof(string.Replace), new[] { typeof(string), typeof(string) });

        var invoke = Expression.Invoke(expr, expr.Parameters);
        var call = Expression.Call(invoke, mi, oldValue, newValue);
        var lambda = Expression.Lambda(call, false, expr.Parameters);

        Console.WriteLine(lambda.Compile().DynamicInvoke("gaga"));

        // Should print empty line. Not throw!
        Console.WriteLine(lambda.Compile().DynamicInvoke(null));
    }
}

Solution

  • You have to do 2 things:

    1. The call lambda.Compile().DynamicInvoke(null) is wrong.

      The documentation states that the parameter can be:

      Type: System.Object[]:
      An array of objects that are the arguments to pass to the method represented by the current delegate.
      -or-
      null, if the method represented by the current delegate does not require arguments.

      So, by passing null you invoke it without parameters, but you would like to invoke with a null string parameter:

      This is why you should change this line to lambda.Compile().DynamicInvoke(new object[] {null}) or simply lambda.Compile().DynamicInvoke((string)null)

    2. You have to add a null condition with Expression.Condition.

    The final code:

    var expr = GetExpression(t => t);
    var oldValue = Expression.Constant("a", typeof(string));
    var newValue = Expression.Constant("o", typeof(string));
    var mi = typeof(string).GetMethod(nameof(string.Replace), new[] { typeof(string), typeof(string) });
    
    var invoke = Expression.Invoke(expr, expr.Parameters);
    var call = Expression.Call(invoke, mi, oldValue, newValue);
    
    ConstantExpression nullConst = Expression.Constant(null, typeof(string));
    var nullCondition = Expression.Condition(Expression.Equal(invoke, nullConst),
        nullConst, call);
    
    var lambda = Expression.Lambda(nullCondition, false, expr.Parameters);
    
    object result1 = lambda.Compile().DynamicInvoke("gaga"); // =="gogo"
    object result2 = lambda.Compile().DynamicInvoke((string) null); //== null