В C#, почему анонимный метод не может содержать оператор yield?



Я подумал, что было бы неплохо сделать что-то вроде этого (с лямбда делает возврат доходности):



public IList<T> Find<T>(Expression<Func<T, bool>> expression) where T : class, new()
{
IList<T> list = GetList<T>();
var fun = expression.Compile();

var items = () => {
foreach (var item in list)
if (fun.Invoke(item))
yield return item; // This is not allowed by C#
}

return items.ToList();
}


однако я обнаружил, что не могу использовать yield в анонимном методе. Мне интересно, почему. Элемент yield docs просто скажите, что это не разрешено.



Так как это не было разрешено, я просто создал список и добавил в него элементы.

435   4  

4 ответов:

Эрик Липперт написал серию сообщений в блоге о том, почему выход не допускается в некоторых случаях.

EDIT2:

  • Часть 7(это было опубликовано позже и конкретно обращается к этому вопросу)

вы, вероятно, найдете там ответ...


EDIT1: это объясняется в комментариях части 5, в ответе Эрика на комментарий Абхиджита Пателя:

Q:

Эрик

можете ли вы также дать некоторое представление о почему "урожайность" не допускается внутри анонимный метод или лямбда-выражение

A:

хорошее вопрос. Я хотел бы иметь блоки анонимных итераторов. Было бы совершенно потрясающе, чтобы иметь возможность строить сам немного генератор последовательности на месте, которое закрылось над местным переменная. Почему не является просто: преимущества не делают перевешивают затраты. Удивительность создание генераторов последовательностей на месте на самом деле довольно маленький в большом схема вещей и номинальные методы выполнить работу достаточно хорошо в большинстве вариант развития событий. Так что пользы нет что неотразимый.

затраты большие. Итератор переписывание-самое сложное преобразований в компиляторе, и анонимный метод переписывания является второй по сложности. Анонимный методы могут быть внутри других анонимных методы и анонимные методы могут быть внутри блоков итератора. Следовательно, то, что мы делаем, это сначала мы переписываем все анонимные методы, чтобы они стали методы класса замыкания. Это второе-последнее дело компилятора перед испускание IL для метода. Как только этот шаг будет выполнен, итератор рерайтер может предположить, что их нет анонимные методы в итераторе блок; они все были переписаны уже. Поэтому итератор рерайтер может просто сосредоточиться на переписывание итератора, без опасаясь, что там может быть нереализованный анонимный метод там.

кроме того, итератор блокирует никогда " гнездо", в отличие от анонимных методов. Итератор рерайтер может предположить, что все итераторы блоки находятся "на высшем уровне".

Если анонимные методы позволили содержат блоки итератора, затем оба эти предположения вылетают в окно. Вы можете иметь блок итератора, который содержит анонимный метод, который содержит анонимный метод, который содержит блок итератора, который содержит анонимный метод, и... фу. Теперь мы должны написать переписывание проход, который может обрабатывать вложенный итератор блоки и вложенные анонимные методы в в то же время, слияние наших двух самых сложные алгоритмы в один далекий более сложный алгоритм. Это очень трудно проектировать, реализовывать, и тест. Мы достаточно умны, чтобы сделать так что я уверен. У нас есть умная команда здесь. Но мы не хотим брать на себя это большое бремя для "приятно иметь но не обязательно " особенность. -- Эрик

Эрик Липперт написал отличную серию статей об ограничениях (и дизайнерских решений, влияющих на этот выбор) на итератор блоков

в частности, блоки итераторов реализуются некоторыми сложными преобразованиями кода компилятора. Эти преобразования будут влиять на преобразования, которые происходят внутри анонимных функций или лямбд, так что в определенных обстоятельствах они оба попытаются "преобразовать" код в какой - то другой конструкция, которая была несовместима с другой.

в результате, они запрещены от взаимодействия.

как блоки итератора работают под капотом хорошо справляется здесь.

как простой пример несовместимости:

public IList<T> GreaterThan<T>(T t)
{
    IList<T> list = GetList<T>();
    var items = () => {
        foreach (var item in list)
            if (fun.Invoke(item))
                yield return item; // This is not allowed by C#
    }

    return items.ToList();
}

компилятор одновременно хочет преобразовать это в нечто вроде:

// inner class
private class Magic
{
    private T t;
    private IList<T> list;
    private Magic(List<T> list, T t) { this.list = list; this.t = t;}

    public IEnumerable<T> DoIt()
    {
        var items = () => {
            foreach (var item in list)
                if (fun.Invoke(item))
                    yield return item;
        }
    }
}

public IList<T> GreaterThan<T>(T t)
{
    var magic = new Magic(GetList<T>(), t)
    var items = magic.DoIt();
    return items.ToList();
}

и в то же время аспект итератора пытается сделать свою работу, чтобы сделать небольшое состояние машина. Некоторые простые примеры могут работать с изрядным количеством проверки здравомыслия (сначала имея дело с (возможно, произвольно) вложенными замыканиями), а затем видя, могут ли результирующие классы самого нижнего уровня быть преобразованы в итераторы.

однако это будет

  1. довольно много работы.
  2. не может работать во всех случаях, если по крайней мере аспект блока итератора не может предотвратить аспект закрытия применение определенных преобразований для повышения эффективности (например, продвижение локальных переменных к переменным экземпляра, а не к полноценному классу закрытия).
    • если бы была даже небольшая вероятность перекрытия там, где это было невозможно или достаточно трудно не быть реализованным, то количество проблем поддержки в результате, вероятно, будет высоким, так как тонкое нарушение изменения будут потеряны для многих пользователей.
  3. его можно очень легко обойти.

в вашем примере вот так:

public IList<T> Find<T>(Expression<Func<T, bool>> expression) 
    where T : class, new()
{
    return FindInner(expression).ToList();
}

private IEnumerable<T> FindInner<T>(Expression<Func<T, bool>> expression) 
    where T : class, new()
{
    IList<T> list = GetList<T>();
    var fun = expression.Compile();
    foreach (var item in list)
        if (fun.Invoke(item))
            yield return item;
}

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

кроме того, методы итератора используют yield также реализуется через магия компилятора.

Я предполагаю, что один из этих двух делает код неидентифицируемым для другой части магии, и что было решено не тратить время на эту работу для текущих версий компилятора C#. Конечно, это может быть сознательный выбор, и что он просто не работает, потому что никто не думал его выполнять.

для 100% точного вопроса я бы предложил вам использовать Microsoft Connect сайт и сообщить вопрос, я конечно, вы получите что-то полезное взамен.

Я бы сделал так:

IList<T> list = GetList<T>();
var fun = expression.Compile();

return list.Where(item => fun.Invoke(item)).ToList();

конечно, вам нужна система.Ядро.DLL-файл ссылается .Net 3.5, для метод в LINQ. И включают в себя:

using System.Linq;

спасибо,

хитрый

Comments

    Ничего не найдено.