В С#, почему анонимный метод не может содержать инструкцию 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 docs просто говорят, что это запрещено.

Поскольку это не было разрешено, я только что создал List и добавил элементы к нему.

Ответ 1

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

EDIT2:

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

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


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

Q:

Эрик,

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

A:

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

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

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

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

Ответ 2

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

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

Как итератор блокирует работу под капотом, рассматривается .

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

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

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

Однако это было бы

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

В вашем примере:

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

Ответ 3

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

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

Кроме того, методы итератора с использованием yield также реализованы с использованием магии компилятора.

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

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

Ответ 4

Я бы сделал это:

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

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

Конечно, вам нужен файл System.Core.dll, связанный с .NET 3.5 для метода Linq. Включите:

using System.Linq;

Приветствия,

Хитрый