Expresión / árboles de declaración

.net c# expression-trees

Pregunta

Pregunta actualizada más abajo

He estado experimentando con árboles de expresiones en .NET 4 para generar código en tiempo de ejecución y he estado intentando implementar la instrucción foreach creando un árbol de expresiones.

Al final, la expresión debería poder generar un delegado que haga esto:

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
  }
}

Se me ocurrió el siguiente método auxiliar que genera una expresión de bloque de un 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;

}

El siguiente código:

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 este árbol de expresiones:

.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:
    }
}

Esto parece estar bien, pero llamar a Compile en esa expresión da como resultado una excepción:

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

¿No lo definí aquí?

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

?

Por supuesto, el ejemplo aquí está diseñado y aún no tiene un uso práctico, pero estoy tratando de entender los árboles de expresión que tienen cuerpos, para combinarlos dinámicamente en el tiempo de ejecución en el futuro.


EDIT: Mi problema inicial fue resuelto por Alexandra, gracias! Por supuesto, me he encontrado con el siguiente problema ahora. He declarado un BlockExpression que tiene una variable en él. Dentro de esa expresión, quiero otra expresión que haga referencia a esa variable. Pero no tengo una referencia real a esa variable, solo su nombre, porque la expresión se suministra externamente.

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

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

La variable body se pasa externamente y no tiene referencia directa a param , pero sí conoce el nombre de la variable en la expresión ( "something" ). Se parece a esto:

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

Este es el "código" que esto 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)
    }
}

Sin embargo, no se compila. Con el mismo error que antes.

TLDR: ¿Cómo puedo hacer referencia a una variable por identificador en un árbol de expresión?

Respuesta aceptada

Su problema es que no pasó parámetros y variables a su expresión de bloque. Los usas en las expresiones "internas", pero la expresión de bloque no sabe nada sobre ellas. Básicamente, todo lo que necesita hacer es pasar todos sus parámetros y variables a una expresión de bloque.

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

Respuesta popular

No se olvide de disponer IEnumerator en probar / finalmente: muchos de los códigos (como File.ReadLines ()) dependen de eso.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow