2009-12-20 13 views
12

Aggiornato domanda, più in bassoEspressione/Dichiarazione

ho avuto modo di sperimentare con alberi di espressione in .NET 4 per generare il codice in fase di esecuzione e ho cercato di implementare l'istruzione foreach con la costruzione di un albero di espressione .

Alla fine, l'espressione dovrebbe essere in grado di generare un delegato che fa questo:

Action<IEnumerable<int>> action = source => 
{ 
    var enumerator = source.GetEnumerator(); 
    while(enumerator.MoveNext()) 
    { 
    var i = enumerator.Current; 
    // the body of the foreach that I don't currently have yet 
    } 
} 

mi è venuta in mente il seguente metodo di supporto che genera un BlockExpression da un oggetto IEnumerable:

public static BlockExpression ForEachExpr<T>(this IEnumerable<T> source, string collectionName, string itemName) 
{ 
     var item = Expression.Variable(typeof(T), itemName); 

     var enumerator = Expression.Variable(typeof(IEnumerator<T>), "enumerator"); 

     var param = Expression.Parameter(typeof(IEnumerable<T>), collectionName); 

     var doMoveNext = Expression.Call(enumerator, typeof(IEnumerator).GetMethod("MoveNext")); 

     var assignToEnum = Expression.Assign(enumerator, Expression.Call(param, typeof(IEnumerable<T>).GetMethod("GetEnumerator"))); 

     var assignCurrent = Expression.Assign(item, Expression.Property(enumerator, "Current")); 

     var @break = Expression.Label(); 

     var @foreach = Expression.Block(
      assignToEnum, 
      Expression.Loop(
       Expression.IfThenElse(
       Expression.NotEqual(doMoveNext, Expression.Constant(false)), 
        assignCurrent 
       , Expression.Break(@break)) 
      ,@break) 
     ); 
     return @foreach; 

} 

Il seguente codice:

var ints = new List<int> { 1, 2, 3, 4 }; 
var expr = ints.ForEachExpr("ints", "i"); 
var lambda = Expression.Lambda<Action<IEnumerable<int>>>(expr, Expression.Parameter(typeof(IEnumerable<int>), "ints")); 

genera questo albero di espressione:

.Lambda #Lambda1<System.Action`1[System.Collections.Generic.IEnumerable`1[System.Int32]]>(System.Collections.Generic.IEnumerable`1[System.Int32] $ints) 
{ 
    .Block() { 
     $enumerator = .Call $ints.GetEnumerator(); 
     .Loop { 
      .If (.Call $enumerator.MoveNext() != False) { 
       $i = $enumerator.Current 
      } .Else { 
       .Break #Label1 { } 
      } 
     } 
     .LabelTarget #Label1: 
    } 
} 

questo sembra essere OK, ma chiamando Compile su che i risultati di espressione in un'eccezione:

"variable 'enumerator' of type 'System.Collections.Generic.IEnumerator`1[System.Int32]' referenced from scope '', but it is not defined" 

non ci ho definiscono qui:

var enumerator = Expression.Variable(typeof(IEnumerator<T>), "enumerator"); 

?

Ovviamente, l'esempio qui è artificioso e non ha ancora un uso pratico, ma sto cercando di ottenere il blocco degli alberi di espressione che hanno corpi, in modo da combinarli dinamicamente in runtime in futuro.


EDIT: Il mio problema iniziale è stato risolto da Alexandra, grazie! Certo, ho incontrato il prossimo problema ora. Ho dichiarato un BlockExpression che contiene una variabile. Dentro quell'espressione, voglio un'altra espressione che faccia riferimento a quella variabile. Ma non ho un riferimento effettivo a quella variabile, solo il suo nome, perché l'espressione è fornita esternamente.

var param = Expression.Variable(typeof(IEnumerable<T>), "something"); 

var block = Expression.Block(
       new [] { param }, 
       body 
      ); 

La variabile body è passata esternamente e non ha alcun riferimento diretto al param, ma non conoscere il nome della variabile nell'espressione ("something"). Ecco come si presenta:

var body = Expression.Call(typeof(Console).GetMethod("WriteLine",new[] { typeof(bool) }), 
       Expression.Equal(Expression.Parameter(typeof(IEnumerable<int>), "something"), Expression.Constant(null))); 

Questo è il "codice" che questo genera:

.Lambda #Lambda1<System.Action`1[System.Collections.Generic.IEnumerable`1[System.Int32]]>(System.Collections.Generic.IEnumerable`1[System.Int32] $something) 
{ 
    .Block(System.Collections.Generic.IEnumerable`1[System.Int32] $something) { 
     .Call System.Console.WriteLine($something== null) 
    } 
} 

Tuttavia, non viene compilato. Con lo stesso errore di prima.

TLDR: Come faccio a fare riferimento a una variabile per identificatore in un albero di espressioni?

+0

Penso che non puoi farlo. I nomi che dai ai parametri all'interno di un albero di espressioni sono più simili ai nomi amichevoli. In realtà non ne hai affatto bisogno, puoi creare un parametro senza un nome e il sistema genererà qualcosa per te. Ma questo è più per scopi di debug che per qualsiasi altra cosa. Quindi, hai semplicemente creato due parametri con lo stesso nome, ma non un parametro e un riferimento ad esso. Prenderò il tuo esempio al team DLR e chiederò se è un bug o meno che tu possa creare due parametri con lo stesso nome come questo. Ma posso ottenere la risposta solo dopo le vacanze. –

+0

Hm, quindi non è possibile comporre dinamicamente un delegato aggiungendo bit e parti separati in un albero di espressioni? Il mio obiettivo finale è quello di generare codice usando un algoritmo evolutivo e per quello, avrei davvero bisogno di essere in grado di fare riferimento a variabili create in un ambito esterno. Grazie per l'aiuto :) – JulianR

+0

Non dico che :-) Ovviamente è possibile creare un delegato o un metodo statico con alberi di espressione (ho anche un post di blog su questo: http://blogs.msdn.com /csharpfaq/archive/2009/09/14/generating-dynamic-methods-with-expression-trees-in-visual-studio-2010.aspx) Ma probabilmente devi refactoring questo pezzo di codice esatto, in modo che "corpo "dovrebbe ottenere un riferimento reale a" param "non solo al nome di una stringa. –

risposta

12

Il problema è che non hai passato parametri e variabili all'espressione di blocco. Li usi nelle espressioni "interne", ma l'espressione di blocco non ne sa nulla. Fondamentalmente, tutto ciò che devi fare è passare tutti i parametri e le variabili a un'espressione di blocco.

 var @foreach = Expression.Block(
      new ParameterExpression[] { item, enumerator, param }, 
      assignToEnum, 
      Expression.Loop(
       Expression.IfThenElse(
        Expression.NotEqual(doMoveNext, Expression.Constant(false)), 
        assignCurrent, 
        Expression.Break(@break)) 
      , @break) 
     ); 
+1

Questo mi ha buttato per un cappio per un po '. Penso che il primo parametro in Block sia chiamato "Parameters", ma quello che dovrebbe essere chiamato è "VariableDeclarations". Questa è stata la chiave per risolverlo grazie! –

2

Scusate se questa è la discussione necromanzia, ma nel caso in cui altre persone sono in esecuzione nello stesso o simile problema:

Si può provare a scrivere un ExpressionVisitor che sostituisce un parametro con lo stesso nome e digitare l'espressione del corpo esterno con il parametro variabile che hai dichiarato durante la creazione dell'espressione di blocco. In questo modo il parametro nel corpo sarà lo stesso oggetto del parametro nella dichiarazione di blocco e quindi LambdaExpression dovrebbe essere compilato.

+0

Questa potrebbe essere un'osservazione utile, ma in questo caso non aiuterà (mancata dichiarazione delle variabili locali in 'Expression.Block()'). – svick

5

Non dimenticare di smaltire IEnumerator in try/finally - un sacco di codice (come File.ReadLines()) dipende da quello.