2010-02-07 10 views
16

Quindi ho uno delegate che punta a una funzione che non conosco quando creo l'oggetto delegate. L'oggetto è impostato su una funzione successiva.Alberi di espressione e invocazione di un delegato

Inoltre, desidero creare un albero di espressioni che richiami il delegato con un argomento (nell'interesse della domanda l'argomento può essere 5). Questo è il bit con cui sto lottando; il codice qui sotto mostra ciò che voglio ma non viene compilato.

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

Per questo esempio ho potuto fare (questo è pratico da quando ho bisogno di costruire gli alberi di espressione in fase di esecuzione):

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

Questo rende expr diventano:

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

che sembra per significare che per utilizzare delegatefunc, ho bisogno di produrre il bit value(Test.Program+<>c__DisplayClass0).func.

Quindi, come posso creare un albero di espressioni che invochi un delegato?

+0

[Linq in azione] (http://www.manning.com/marguerie/) ha una sezione dettagliata sugli alberi di espressione. –

risposta

9

OK, questo mostra come può essere fatto (ma è molto poco elegante a mio parere):

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

Essenzialmente Io uso (x) => func(x); per fare una funzione che chiama ciò a cui punta il delegato. Ma puoi vedere che expr è eccessivamente complicato.Per questo motivo non considero questa risposta buona, ma forse può essere costruita su?

+2

L'espressione creata deve avere un modo per accedere alla variabile func per riferimento. Non è possibile creare un riferimento a una variabile locale. Nel tuo codice stai usando il lambda di bind per catturare la variabile locale, e il compilatore C# funziona con la sua magia e crea una classe separata per contenere ciò che assomiglia a una variabile locale. Potresti farlo tu stesso e non dovresti usare l'espressione bind, ma il lambda risultante sarebbe probabilmente altrettanto complicato. –

+2

Sarei interessato a vedere il "metodo fai da te". Immagino che richiederebbe un po 'di magia da Reflection.Emit? –

2

questo dovrebbe funzionare:

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 

espressioni possono essere un mindfuck, ma ricordate: espressioni sono sempre costruiti da altre espressioni. Un'espressione è un albero di altre espressioni che descrive il codice. Non puoi passare al delegato effettivo come fai nel tuo esempio, ciò di cui hai bisogno è un'espressione di quel delegato, dicendo che l'espressione si aspetta un parametro del tipo del tuo delegato. Quindi dici di voler chiamare un metodo su quel parametro, ovvero il metodo Invoke, con l'argomento '5'. Tutte le altre cose dopo è solo se si desidera trasformare l'espressione in codice eseguibile, cosa che probabilmente si fa.

L'ho eseguito con .NET4, spero di non averlo mescolato con le sole espressioni .NET.

EDIT In risposta al commento di PythonPower:

Penso che ciò che si vuole (non passando il delegato come argomento) può essere fatto solo quando il delegato in sé è descritto come espressione, in questo modo:

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 

L'unico altro modo in cui posso pensare è quello di catturare un delegato dichiarato localmente in una chiusura, ma non saprei come farlo.

+0

Solo una piccola modifica per farlo funzionare con .NET Framework 3.5 – Kane

+0

Questo è molto vicino a quello che voglio, ma non voglio passare il delegato come argomento. –

+0

La modifica presuppone che conosca la funzione ma non conosco la funzione fino a quando non viene creata l'espressione. E la funzione non è necessariamente un'espressione stessa. L'idea di chiusura sembra promettente. –

13

Penso che quello che si vuole fare è usare le proprietà Target e Method del delegato da passare per creare un'espressione Call. Basandosi sul campione di JulianR, questo è ciò che sarebbe simile:

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

Devo sostituire Expression.Constant (func.Target) con null per farlo funzionare. Ma ciò si lega a ciò che la funzione func indica attualmente non a ciò che potrebbe indicare in seguito. Il problema è che nessun presupposto su quali funzioni può essere fatto e può cambiare in qualsiasi momento. –

+0

Questa è quasi la risposta di cui avevo bisogno, ma per favore rimuovi il primo parametro da Expression.Call. Altrimenti, c'è un'eccezione (ho verificato sia su .NET4 che su .NET4.5) – ironic

+0

@ironic: il metodo di destinazione era un metodo statico durante il test? Questa potrebbe essere la differenza. –

Problemi correlati