Деревья выражений и вызов делегата
Итак, у меня есть delegate
, который указывает на некоторую функцию, о которой я действительно не знаю, когда я впервые создаю объект delegate
. Объект будет установлен на некоторую функцию позже.
Затем я также хочу создать дерево выражений, которое вызывает делегат с аргументом (для этого вопроса аргумент может быть 5
). Это бит, с которым я борюсь; приведенный ниже код показывает, что я хочу, но он не компилируется.
Func<int, int> func = null;
Expression expr = Expression.Invoke(func, Expression.Constant(5));
В этом примере я мог бы сделать (это практично, так как мне нужно построить деревья выражений во время выполнения):
Func<int, int> func = null;
Expression<Func<int>> expr = () => func(5);
Это означает, что expr
станет:
() => Invoke(value(Test.Program+<>c__DisplayClass0).func, 5)
Что, по-видимому, означает, что для использования delegate
func
мне нужно создать бит value(Test.Program+<>c__DisplayClass0).func
.
Итак, как я могу создать дерево выражений, которое вызывает делегат?
Ответы
Ответ 1
ОК, это показывает, как это можно сделать (но на мой взгляд это очень нелогично):
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();
По существу я использую (x) => func(x);
для создания функции, которая вызывает то, на что указывает делегат. Но вы можете видеть, что expr
слишком сложно. По этой причине я не считаю этот ответ хорошим, но, возможно, на нем можно построить?
Ответ 2
Я думаю, что вы хотите использовать свойства Target и Method для передачи делегата для создания выражения Call. На основе образца JulianR это будет выглядеть так:
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
Ответ 3
Это должно работать:
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
Выражения могут быть мозгом, но помните: выражения всегда создаются из других выражений. Выражение - это дерево других выражений, которое описывает код. Вы не можете передать фактический делегат, как в своем примере, то, что вам нужно, является выражением этого делегата, говоря, что выражение ожидает параметр типа вашего делегата. Затем вы говорите, что хотите вызвать метод по этому параметру, а именно метод Invoke
, с аргументом "5". Все остальное после этого - просто, если вы хотите превратить выражение в исполняемый код, который вы, вероятно, делаете.
Я запускал это с .NET4, хотя, надеюсь, я не смешивал в .NET4 только материал выражения.
РЕДАКТИРОВАТЬ В ответ на комментарий PythonPower:
Я думаю, что то, что вы хотите (не передавая делегату в качестве аргумента), может быть выполнено только тогда, когда сам делегат описывается как выражение:
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
Единственный другой способ, о котором я могу думать, - захватить делегат, объявленный локально в закрытии, но я не знаю, как это сделать.