Expression and delegate in c#

2019-08-15 02:20发布

I have below code that is a pseudo-code. I want to make this function can accept a Expresstion type, compile this expression and invoke it with proper parameters.

public static void f<T>(Expression<T> exp, params dynamic[] d)
{
    Console.WriteLine("begin");
    exp.Compile().Invoke(d[0],d[1].....);//this is pseudo-code

    Console.WriteLine("end");
}

I'm sure the T is an Action type. (T can be Action,Action<int>,etc.). The parameter d is a array of dynamic type, which is sent to invoke.

But I don't know how to finish the code. I'm sure that's not easy to implement it. Perhaps it can't be true in c#

2条回答
干净又极端
2楼-- · 2019-08-15 02:52

You can't use Invoke unless you know the exact signature. You can, however, use DynamicInvoke, for example:

((Delegate)exp.Compile()).DynamicInvoke(d);

Note that the dynamic in the above serves no purpose - d could just as well be object[].

The other, slightly more complicated, approach - would be to compile it as a Func<object[]>, and re-write the expression (ExpressionVisitor) to replace "parameter n" (from the original exp) with p[n], where p is a single ParameterExpression and n is a ConstantExpression of n. This might be advantageous if you were going to store and aggressively re-use the compiled lambda. But in your specific scenario you are compiling per call, so this would have no benefit.

Here's an example, but this is mainly intended for later readers with similar scenarios, but where the compiled delegate is re-used; the "advantage" of this re-writing is that it avoids the performance impact of Delegate.DynamicInvoke, while retaining the object[] => object signature of Delegate.DynamicInvoke; but this will only be useful if the delegate is being used multiple times. At the moment (compiled per call) most of the "work" here is going to be in the expression-compile and JIT-compile.

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
static class Program {
    static void Main() {
        Expression<Func<int, float, double>> exp = (i, f) => i * f;
        var func = CompileToBasicType(exp);

        object[] args = { 3, 2.3F };
        object result = func(args); // 6.9 (double)
    }

    static Func<object[], object> CompileToBasicType(LambdaExpression exp) {
        ParameterExpression arg =
            Expression.Parameter(typeof(object[]), "args");
        Dictionary<Expression, Expression> lookup =
            new Dictionary<Expression, Expression>();
        int i = 0;
        foreach (var p in exp.Parameters) {
            lookup.Add(p, Expression.Convert(Expression.ArrayIndex(
                arg, Expression.Constant(i++)), p.Type));
        }
        var body = Expression.Convert(
            new ReplaceVisitor(lookup).Visit(exp.Body), typeof(object));
        return Expression.Lambda<Func<object[], object>>(body, arg).Compile();
    }
    class ReplaceVisitor : ExpressionVisitor {
        private readonly Dictionary<Expression, Expression> lookup;
        public ReplaceVisitor(Dictionary<Expression, Expression> lookup) {
            if (lookup == null) throw new ArgumentNullException("lookup");
            this.lookup= lookup;
        }
        public override Expression Visit(Expression node) {
            Expression found;
            return lookup.TryGetValue(node, out found) ? found
                : base.Visit(node);
        }
    }
}
查看更多
虎瘦雄心在
3楼-- · 2019-08-15 02:55
public static void F<T>(Expression<T> exp, params object[] d)
{
    Console.WriteLine("begin");

    var del = exp.Compile() as Delegate;
    del.DynamicInvoke(d);

    Console.WriteLine("end");
}

And then:

F<Action<int>>(i => Console.WriteLine(i), 5);

or:

F<Action<string, int>>((s, i) => Console.WriteLine("{0} : {1}", s, i), "Hello", 5);
查看更多
登录 后发表回答