Expression Trees and Invoking a Delegate
So I have a delegate
which points to some function which I don't actually know about when I first create the delegate
object. The object is set to some function later.
I also then want to make an expression tree that invokes the delegate with an argument (for this question's sake the argument can 开发者_如何学Pythonbe 5
). This is the bit I'm struggling with; the code below shows what I want but it doesn't compile.
Func<int, int> func = null;
Expression expr = Expression.Invoke(func, Expression.Constant(5));
For this example I could do (this is practical since I need to build the expression trees at runtime):
Func<int, int> func = null;
Expression<Func<int>> expr = () => func(5);
This makes expr
become:
() => Invoke(value(Test.Program+<>c__DisplayClass0).func, 5)
Which seems to mean that to use the delegate
func
, I need to produce the value(Test.Program+<>c__DisplayClass0).func
bit.
So, how can I make an expression tree which invokes a delegate?
I think what you want to do is use the Target and Method properties of the delegate to pass to create a Call expression. Building on JulianR's sample, this is what it would look like:
Action<int> func = i => Console.WriteLine(i * i);
var callExpr = Expression.Call(Expression.Constant(func.Target), func.Method, Expression.Constant(5));
var lambdaExpr = Expression.Lambda<Action>(callExpr);
var fn = lambdaExpr.Compile();
fn(); // Prints 25
OK, this shows how it can be done (but it is very inelegant in my opinion):
Func<int, int> func = null;
Expression<Func<int, int>> bind = (x) => func(x);
Expression expr = Expression.Invoke(bind, Expression.Constant(5));
Expression<Func<int>> lambda = Expression.Lambda<Func<int>>(expr);
Func<int> compiled = lambda.Compile();
Console.WriteLine(expr);
func = x => 3 * x;
Console.WriteLine(compiled());
func = x => 7 * x;
Console.WriteLine(compiled());
Console.Read();
Essentially I use (x) => func(x);
to make a function that calls what the delegate points to. But you can see that expr
is overly complicated. For this reason I don't consider this answer good, but maybe it can be built upon?
While other answers provide some working ways there's a shorter one:
Expression.Invoke(Expression.Constant(my_delegate), parameter_for_delegate)
It works both for delegates referencing static methods and instance methods with no change.
This should work:
Action<int> func = i => Console.WriteLine(i * i);
// If func is null like in your example, the GetType() call fails,
// so give it a body or use typeof if you know the type at compile time
var param = Expression.Parameter(func.GetType());
// Call the Invoke method on the delegate, which is the same as invoking() it
var callExpr = Expression.Call(param, func.GetType().GetMethod("Invoke"), Expression.Constant(5));
var lambdaExpr = Expression.Lambda<Action<Action<int>>>(callExpr, param);
var fn = lambdaExpr.Compile(); // Compile the expression tree so it can be executed
fn(func); // Prints 25
Expressions can be a mindfuck, but remember: expressions are always built up out of other expressions. An expression is a tree of other expressions that describes code. You can't pass in the actual delegate like you do in your example, what you need is an expression of that delegate, by saying the expression expects a parameter of the type of your delegate. Then you say you want to call a method on that parameter, namely the Invoke
method, with the argument '5'. All the other stuff after that is just if you want to turn the expression into runnable code, which you probably do.
I ran this with .NET4 though, I hope I haven't mixed in .NET4 only expression stuff.
EDIT In response to PythonPower's comment:
I think what you want (not passing in the delegate as an argument) can only be done when the delegate itself is described as an expression, like this:
var arg = Expression.Parameter(typeof(int), "i");
var multiply = Expression.Multiply(arg, arg);
var writeln = Expression.Call(typeof(Console).GetMethod("WriteLine",
new[] { typeof(int) }), multiply);
var lambda = Expression.Lambda<Action<int>>(writeln, arg);
var compiled = lambda.Compile();
compiled(5); // Prints 25
The only other way I can think of is to capture an delegate declared locally in a closure, but I wouldn't know how to do that.
精彩评论