Search code examples
c#lambdaexpression-trees

Linq Expression from Lambda: specify parameter explicitly


I want to embed an expression tree such as

Expression<Func<MyObject, double>> expr = (o) => o.Value;

into a larger expression tree generated by a parser. However, the parameter o is already defined inside the outer expression tree. In principle I would have to search the body of expr and replace all occurences of the parameter by the instance from the parsed expression tree.

Is there a built in way to do this? Or is there even a way to directly generate the lambda expression while specifying the instance of the parameter in advance?


Solution

  • You can't directly instruct the compiler to reuse your existing ParameterExpression instances, but you can definitely replace them (in effect creating new expression trees) afterwards.

    The built-in ExpressionVisitor helps a lot with the heavy lifting; it's a no-op visitor that you derive from to add the required functionality. In this case you need to instruct it to replace ParameterExpression instances, so you could have this:

    // Sorry for the atrocious formatting, wanted to keep it scrollbar-free
    class ParameterReplacementVisitor : ExpressionVisitor
    {
        private readonly
        IEnumerable<KeyValuePair<ParameterExpression, ParameterExpression>>
        replacementMap;
    
        public ParameterReplacementVisitor(
            IEnumerable<KeyValuePair<ParameterExpression, ParameterExpression>> map)
        {
            this.replacementMap = map;
        }
    
        protected override Expression VisitLambda<T>(Expression<T> node)
        {
            return Expression.Lambda<T>(
                Visit(node.Body),
                node.Parameters.Select(Visit).Cast<ParameterExpression>());
        }
    
        protected override Expression VisitParameter(ParameterExpression node)
        {
            var replacement = this.replacementMap
                                  .Where(p => p.Key == node)
                                  .DefaultIfEmpty()
                                  .First().Value;
            return base.VisitParameter(replacement ?? node);
        }
    }
    

    which you can use like this:

    Expression<Func<int, bool>> e1 = i => true;
    Expression<Func<int, bool>> e2 = j => false;
    
    Console.WriteLine(e1.Parameters[0] == e2.Parameters[0]); // false
    
    var replacements = new Dictionary<ParameterExpression, ParameterExpression>
    {
        { e1.Parameters[0], e2.Parameters[0] }
    };
    
    var replacer = new ParameterReplacementVisitor(replacements);
    var e3 = replacer.VisitAndConvert(e1, "replacing parameters");
    
    Console.WriteLine(e3.Parameters[0] == e2.Parameters[0]); // true