В 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 в анонимном методе. Интересно, почему? The yield docs просто скажите, что это не разрешено.

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

4 ответов


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

EDIT2:

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

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


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

Q:

Эрик

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

A:

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

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

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

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


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

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

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

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

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

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;

спасибо,

хитрый