How to compile MethodCallExpression with operations on arguments?

c# expression-trees lambda

Question

I have a method Method

private static int Method(int n)
{
    return n;
}

I get MethodCallExpression in overrided VisitMethodCall in my ExpressionVisitor. MethodCallExpression contains:

n => Method(2 + n)

I want to compile it to Func and call like this:

func(3)

And it's should return 5.

I tried this:

    IEnumerable<ParameterExpression> parameters = expression.Arguments.Select(a => Expression.Parameter(a.Type, a.ToString()));
    MethodCallExpression call = Expression.Call(expression.Method, parameters);
    Expression<Func<Int32, Int32>> lambda = Expression.Lambda<Func<int, int>>(call, call.Arguments.OfType<ParameterExpression>());
    var func = lambda.Compile();
    Console.WriteLine(func(3));

And it's return me 3, not 5

It is because 2+x it is param name and I replace it with 3.

1
2
12/15/2017 11:47:23 AM

Accepted Answer

Not sure why you would ever do this, but anyway to do this you need to extract parameters (not arguments) used by your MethodCallExpression. For that you can abuse expression visitor like this:

public class ParametersExtractorVisitor : ExpressionVisitor {
    public IList<ParameterExpression> ExtractedParameters { get; } = new List<ParameterExpression>();
    protected override Expression VisitParameter(ParameterExpression node) {
        ExtractedParameters.Add(node);
        return base.VisitParameter(node);
    }
}

Then use it like this in your code:

var visitor = new ParametersExtractorVisitor();
visitor.Visit(expression);
MethodCallExpression call = Expression.Call(expression.Method, expression.Arguments);
Expression<Func<Int32, Int32>> lambda = Expression.Lambda<Func<int, int>>(call, visitor.ExtractedParameters);
var func = lambda.Compile();
Console.WriteLine(func(3));
1
12/15/2017 12:25:18 PM

Popular Answer

You don't need a visitor to implement it.
Basically your Method function should be supplied with the result of an addition operation of the value passed to the lambda with the constant value of 2.

using System;
using System.Linq.Expressions;
using System.Reflection;

namespace Test
{
    class Program
    {
        static void Main(string[] args) {
            var method = typeof(Program).GetMethod("Method", BindingFlags.Static | BindingFlags.Public);
            var parameter = Expression.Parameter(typeof(int), "n");
            var add = Expression.Add(Expression.Constant(2, typeof(int)), parameter);
            var methodCallExpression = Expression.Call(null, method, add);
            var lambda = Expression.Lambda<Func<int, int>>(methodCallExpression, parameter);
            var func = lambda.Compile();

            Console.WriteLine(func(3));
        }

        public static int Method(int n) => n;
    }
}

I would implement it using the visitor by mutating the MethodCallExpression

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Test
{

    class MethodCallVisitor : ExpressionVisitor
    {
        private readonly int toAdd;

        public MethodCallVisitor(int toAdd) {
            this.toAdd = toAdd;
        }

        protected override Expression VisitMethodCall(MethodCallExpression node) {
            var add = Expression.Add(node.Arguments.First(), Expression.Constant(toAdd));

            return Expression.Call(node.Object, node.Method, add);
        }
    }

    class Program
    {
        static void Main(string[] args) {
            var methodCallVisitor = new MethodCallVisitor(2);
            var method = typeof(Program).GetMethod("Method", BindingFlags.Static | BindingFlags.Public);
            var parameter = Expression.Parameter(typeof(int), "n");
            var methodCallExpression = Expression.Call(null, method, parameter);
            var lambda = Expression.Lambda<Func<int, int>>(methodCallExpression, parameter);

            lambda = (Expression<Func<int, int>>)methodCallVisitor.Visit(lambda);
            var func = lambda.Compile();

            Console.WriteLine(func(3));
        }

        public static int Method(int n) => n;
    }
}


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