Деревья выражения/Оператора

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

Я экспериментировал с деревьями выражений в.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");

?

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


Править: Моя начальная проблема была решена Alexandra, Спасибо! Конечно, я столкнулся со следующей проблемой теперь. Я объявил a 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)
    }
}

Однако это не компилирует. С той же ошибкой как прежде.

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

12
задан svick 15 May 2013 в 19:44
поделиться

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)
        );
12
ответ дан 2 December 2019 в 18:54
поделиться
Другие вопросы по тегам:

Похожие вопросы: