2010-08-25 12 views
46

Sto cercando di capire come mettere insieme tutti i pezzi e apprezzerei un esempio di codice sorgente concreto per un caso semplice con cui iniziare.C# 4 "dinamico" negli alberi di espressione

Si consideri il seguente codice C#:

Func<int, int, int> f = (x, y) => x + y; 

posso produrre una funzione equivalente in fase di esecuzione utilizzando alberi di espressione come segue:

var x = Expression.Parameter(typeof(int), "x"); 
var y = Expression.Parameter(typeof(int), "y"); 
Func<int, int, int> f = 
    Expression.Lambda<Func<int, int, int>>(
     Expression.Add(x, y), 
     new[] { x, y } 
    ).Compile(); 

Ora dato il seguente lambda:

Func<dynamic, dynamic, dynamic> f = (x, y) => x + y; 

come potrei generare l'equivalente usando gli alberi di espressione (e, presumibilmente, Expression.Dynamic)?

risposta

49

È possibile creare un albero di espressioni che rappresenti un'espressione di aggiunta C# dinamica passando il CallSiteBinder per un'espressione di aggiunta C# dinamica in Expression.Dynamic. È possibile scoprire il codice per creare il Raccoglitore eseguendo Reflector sull'espressione dinamica originale. Il tuo esempio sarebbe simile a questo:

var x = Expression.Parameter(typeof(object), "x"); 
var y = Expression.Parameter(typeof(object), "y"); 
var binder = Binder.BinaryOperation(
    CSharpBinderFlags.None, ExpressionType.Add, typeof(Program), 
    new CSharpArgumentInfo[] { 
     CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
     CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)}); 
Func<dynamic, dynamic, dynamic> f = 
    Expression.Lambda<Func<object, object, object>>(
     Expression.Dynamic(binder, typeof(object), x, y), 
     new[] { x, y } 
    ).Compile(); 
+0

Fresco, molto interessante. Non sono sicuro che tu abbia risposto alla domanda dell'OP, ma sicuramente la stai affrontando nel modo in cui vorrebbe. –

+0

Meraviglioso, questo è proprio quello che volevo, grazie! Un'altra cosa che non mi è del tutto chiara dall'osservazione dei documenti MSDN per Binder.BinaryOperation (http://msdn.microsoft.com/en-us/library/ee814532.aspx) - qual è il significato del "contesto" parametro? C# usa sempre il nome del tipo di allegato lì? Ha qualche significato semantico speciale oltre a contrassegnare un particolare sito di chiamata come diverso da tutti gli altri siti di chiamata (a fini di memorizzazione nella cache, presumo)? Se, per esempio, ho due metodi in una stessa classe che probabilmente spediranno le stesse cose in modo diverso, creo due classi fittizie? –

+0

@Pavel: Penso che sia usato per determinare quali membri sono accessibili. Ad esempio, se hai 'private void Foo (string s) {} public void Foo (object o) {}', chiamando Foo su una stringa all'interno della classe sceglierà l'overload della stringa, ma al di fuori della classe sceglierà il sovraccarico dell'oggetto . – Quartermeister

1

Non è possibile farlo perché un albero di espressioni "Potrebbe non contenere un'operazione dinamica".

Di seguito sarà non compilare, a causa del funzionamento +, per esempio, e si sta cercando di costruire un albero di espressione che viola questa regola:

Expression<Func<dynamic, dynamic, dynamic>> f = (x, y) => x + y; 

Se non si stava facendo un'operazione di somma si potrebbe farla franca

Vedere How to create an Expression<Func<dynamic, dynamic>> - Or is it a bug? per ulteriori informazioni.

Edit:

Questo è il più vicino che posso ottenere, attraverso la definizione di mio metodo Add che accetta parametri dinamici e restituisce un risultato dinamico.

class Program 
{ 
    static void Main(string[] args) 
    { 

     var x = Expression.Parameter(typeof(object), "x"); 
     var y = Expression.Parameter(typeof(object), "y"); 
     Func<dynamic, dynamic, dynamic> f = 
      Expression.Lambda<Func<dynamic, dynamic, dynamic>>(
       Expression.Call(typeof(Program), "Add", null, x, y), 
       new[] { x, y } 
      ).Compile(); 

     Console.WriteLine(f(5, 2)); 
     Console.ReadKey(); 
    } 

    public static dynamic Add(dynamic x, dynamic y) 
    { 
     return x + y; 
    } 
} 
+2

Penso che questo sia sbagliato. L'errore del compilatore che hai citato (di cui sono a conoscenza) indica che si tratta di una limitazione C#, ma non necessariamente che si tratta di una limitazione di alberi di espressione in quanto tale. Dopotutto, C# non consente 'if' o' while' (o statement lambdas in generale) nel contesto dell'albero delle espressioni, ma è possibile costruire manualmente un albero di espressioni di questo tipo (in .NET 4). Ma la ragione principale per cui credo sia possibile è perché c'è 'Expression.Dynamic'. Sono abbastanza sicuro che la risposta implicherebbe questo, semplicemente non so come, esattamente (e la documentazione è sottile). –

+1

A proposito, nella domanda a cui ti sei collegato, c'è un commento di Eric Lippert che dice "I codegen che generiamo per quelle operazioni dinamiche in fase di compilazione che implementano la semantica dinamica in fase di runtime sono estremamente complessi; non esiste un modo __easy__ per rappresentarlo __cleanly__ in un albero di espressioni. " - nota la parte evidenziata. Quindi è possibile, solo abbastanza complicato da essere tagliato per essere troppo costoso e dare troppo poco valore. –

+0

Limitazione del compilatore o altrimenti ... non è possibile al momento. Divertiti a provare! Dovrai farlo in IL. Come hai detto Eric Lippert ha scritto che hanno lasciato fuori quella caratteristica. –

0

Molto interessante. Credo che sia impossibile per la stessa ragione la seguente non viene compilato:

Expression<Func<dynamic, dynamic, int>> func = (p1, p2) => p1 + p2; 

E 'una CS1963 errore del compilatore (che non sembra essere documentato da MS):

errore CS1963: Un'espressione albero non può contenere un'operazione dinamica

+0

Sembra una limitazione del compilatore C#, non come una limitazione dell'API dell'albero delle espressioni. Vedi la risposta a Richard per i dettagli. –

Problemi correlati