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?
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. –
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
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. –