2010-02-07 9 views
16

Tengo un delegate que apunta a alguna función que no conozco cuando creo el objeto delegate. El objeto está configurado para alguna función más tarde.Árboles de expresiones e invocando a un delegado

También quiero hacer un árbol de expresiones que invoca al delegado con un argumento (por el bien de esta pregunta, el argumento puede ser 5). Este es el tema con el que estoy luchando; el siguiente código muestra lo que quiero, pero no compila.

Func<int, int> func = null; 
Expression expr = Expression.Invoke(func, Expression.Constant(5)); 

Para este ejemplo que podía hacer (esto es práctico ya que necesito para construir los árboles de expresión en tiempo de ejecución):

Func<int, int> func = null; 
Expression<Func<int>> expr =() => func(5); 

Esto hace expr se convierten en:

() => Invoke(value(Test.Program+<>c__DisplayClass0).func, 5) 

que parece para significar que para usar el delegatefunc, necesito producir el bit value(Test.Program+<>c__DisplayClass0).func.

Entonces, ¿cómo puedo crear un árbol de expresiones que invoque a un delegado?

+0

[Linq en acción] (http://www.manning.com/marguerie/) tiene una sección detallada sobre árboles de expresiones. –

Respuesta

9

OK, esto demuestra cómo puede hacerse (pero es muy poco elegante en mi opinión):

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(); 

Esencialmente Yo uso (x) => func(x); para hacer una función que llame a lo que señala el delegado. Pero puede ver que expr es demasiado complicado.Por esta razón, no considero que esta respuesta sea buena, pero ¿tal vez se pueda construir sobre ella?

+2

La expresión que crea necesita tener una forma de acceder a la variable de func por referencia. No puede crear una referencia a una variable local. En su código está utilizando el enlace lambda para capturar la variable local, y el compilador de C# funciona de manera mágica y crea una clase separada para contener lo que parece una variable local. Podrías hacerlo tú mismo y no tendrías que usar la expresión bind, pero la lambda resultante probablemente sería igual de complicada. –

+2

Me interesaría ver el "método hágalo usted mismo". Me imagino que requeriría algo de magia de Reflection.Emit? –

2

esto debería funcionar:

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 

Las expresiones pueden ser un mindfuck, pero recuerda: expresiones siempre se construyen a partir de otras expresiones. Una expresión es un árbol de otras expresiones que describe el código. No puede pasar el delegado real como lo hace en su ejemplo, lo que necesita es una expresión de ese delegado, diciendo que la expresión espera un parámetro del tipo de su delegado. Luego dices que quieres llamar a un método en ese parámetro, a saber, el método Invoke, con el argumento '5'. Todo lo demás después de eso es solo si quieres convertir la expresión en código ejecutable, lo que probablemente harás.

Sin embargo, ejecuté esto con .NET4, espero no haber mezclado elementos de expresiones solo en .NET4.

EDITAR En respuesta al comentario de PythonPower:

Creo que lo que quiere (que no pasa en el delegado como un argumento), sólo se puede hacer cuando el delegado misma se describe como una expresión, como esto:

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 

La única otra forma en que puedo pensar es capturar un delegado declarado localmente en un cierre, pero no sabría cómo hacerlo.

+0

Sólo un pequeño cambio para que funcione con .NET Framework 3.5 – Kane

+0

Esto está muy cerca de lo que quiero, pero no quiero pasar el delegado como argumento. –

+0

La edición asume que conozco la función, pero no sé la función hasta después de crearla. Y la función no es necesariamente una expresión en sí misma. La idea de cierre suena prometedora. –

13

Creo que lo que quiere hacer es usar las propiedades de Destino y Método del delegado para pasar para crear una expresión de Llamada. Sobre la base de la muestra de julianr, esto es lo que se vería así:

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 
+0

Tengo que reemplazar Expression.Constant (func.Target) con null para que funcione. Pero eso se une a lo que la función func señala actualmente no es lo que puede señalar más adelante. El problema es que no se puede hacer ninguna suposición sobre qué funciones se pueden establecer y puede cambiar en cualquier momento. –

+0

Esta es casi la respuesta que necesitaba, pero elimine el primer parámetro de Expression.Call. De lo contrario, hay una excepción (he comprobado en .NET4 y .NET4.5) – ironic

+0

@ironic: ¿El método de destino era un método estático cuando estuvo realizando la prueba? Esa podría ser la diferencia. –

Cuestiones relacionadas