Подтвердить что ты не робот

Деревья выражений/выражений

Обновленный вопрос далее вниз

Я экспериментировал с деревьями выражений в .NET 4 для генерации кода во время выполнения, и я пытался реализовать инструкцию foreach, построив дерево выражений.

В конце выражение должно иметь возможность генерировать делегат, который делает это:

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

Я придумал следующий вспомогательный метод, который генерирует BlockExpression из 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;

}

Следующий код:

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

Создает это дерево выражений:

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

Кажется, что все в порядке, но вызов Compile в этом выражении приводит к исключению:

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

Я не определил его здесь:

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

?

Конечно, пример здесь изобретен и пока не имеет практического применения, но я пытаюсь получить висячие деревья выражений, которые имеют тела, чтобы динамически объединить их во время выполнения в будущем.


EDIT: Моя первоначальная проблема была решена Александрой, спасибо! Конечно, сейчас я столкнулся с следующей проблемой. Я объявил BlockExpression, в котором есть переменная. Внутри этого выражения я хочу другое выражение, которое ссылается на эту переменную. Но у меня нет фактической ссылки на эту переменную, просто ее имя, потому что выражение поставляется снаружи.

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

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

Переменная body передается извне и не имеет прямой ссылки на param, но знает имя переменной в выражении ("something"). Это выглядит так:

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

Это "код", который генерирует:

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

Однако он не компилируется. С той же ошибкой, что и раньше.

TL;DR: Как ссылаться на переменную по идентификатору в дереве выражений?

4b9b3361

Ответ 1

Проблема заключается в том, что вы не передавали параметры и переменные вашему блочному выражению. Вы используете их во внутренних выражениях, но блочное выражение ничего не знает о них. В основном, все, что вам нужно сделать, - передать все ваши параметры и переменные блочному выражению.

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

Ответ 2

Не забудьте оставить IEnumerator в try/finally - от этого зависит много кода (например, File.ReadLines()).

Ответ 3

Извините, если это некромантия нити, но в случае, если другие люди сталкиваются с той же или подобной проблемой:

Вы можете попытаться написать ExpressionVisitor, который заменяет параметр с тем же именем и вводит внешнее выражение тела с параметром переменной, который вы указали при создании блочного выражения. Таким образом, параметр в теле будет тем же объектом, что и параметр в объявлении блока, и поэтому компиляция LambdaExpression должна компилироваться.