Деревья выражений и вызов делегата - PullRequest
17 голосов
/ 07 февраля 2010

Итак, у меня есть 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.

Итак, как я могу создать дерево выражений, которое вызывает делегат?

Ответы [ 4 ]

13 голосов
/ 07 февраля 2010

Я думаю, что вы хотите сделать, это использовать свойства 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
10 голосов
/ 07 февраля 2010

ОК, это показывает, как это можно сделать (но, на мой взгляд, это очень не элегантно):

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 голосов
/ 07 февраля 2010

Это должно работать:

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

Единственный другой способ, который я могу придумать, - это захват делегата, объявленного локально, в закрытии, но я не знаю, как это сделать.

1 голос
/ 20 августа 2018

В то время как другие ответы предоставляют некоторые способы работы, есть более короткий:

Expression.Invoke(Expression.Constant(my_delegate), parameter_for_delegate)

Работает как для делегатов, ссылающихся на статические методы, так и на методы экземпляров без изменений.

...