Aggiornamento della domanda ulteriormente giù
Ho sperimentato con alberi di espressioni in .NET 4 per generare codice in fase di esecuzione e ho cercato di implementare l'istruzione foreach
costruendo un albero di espressioni.
Alla fine, l'espressione dovrebbe essere in grado di generare un delegato che faccia 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
}
}
Sono arrivato con 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 espressioni:
.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 quell'espressione si ottiene un'eccezione:
"variable 'enumerator' of type 'System.Collections.Generic.IEnumerator`1[System.Int32]' referenced from scope '', but it is not defined"
Non l'ho definito qui:
var enumerator = Expression.Variable(typeof(IEnumerator<T>), "enumerator");
?
Naturalmente, 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, al fine di 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 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
viene passata esternamente e non ha riferimenti diretti a param
, ma conosce il nome della variabile nell'espressione ( "something"
). Sembra questo:
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 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?
Il tuo problema è che non hai passato parametri e variabili alla tua 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)
);
Non dimenticare di gettare IEnumerator in try / finally - un sacco di codice (come File.ReadLines ()) dipende da questo.