Почему в 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 в анонимном методе. Мне интересно почему. выдают документы , просто говорят, что это запрещено.

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

78 голосов | спросил Lance Fisher 2 AM00000030000000331 2009, 03:10:03

5 ответов


0

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

РЕДАКТИРОВАТЬ 2:

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

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


РЕДАКТИРОВАТЬ1: это объясняется в комментариях к части 5, в ответе Эрика на комментарий Абхиджита Пателя:

Q:

  

Эрик,

     

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

A:

  

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

     

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

     

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

     

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

ответил Thomas Levesque 2 AM00000030000003831 2009, 03:21:38
0

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

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

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

Как хорошо работают блоки итератора, здесь . р>

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

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;
}
ответил ShuggyCoUk 2 AM00000030000002931 2009, 03:18:29
0

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

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

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

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

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

ответил Lasse Vågsæther Karlsen 2 AM00000030000001531 2009, 03:17:15
0

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

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;

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

Хитрый

ответил 20 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowSun, 20 Sep 2009 00:42:58 +0400 2009, 00:42:58
0

Может быть, это просто ограничение синтаксиса. В Visual Basic .NET, которая очень похожа на C #, совершенно возможно писать неловко

 Sub Main()
    Console.Write("x: ")
    Dim x = CInt(Console.ReadLine())
    For Each elem In Iterator Function()
                         Dim i = x
                         Do
                             Yield i
                             i += 1
                             x -= 1
                         Loop Until i = x + 20
                     End Function()
        Console.WriteLine($"{elem} to {x}")
    Next
    Console.ReadKey()
End Sub

Также обратите внимание на круглые скобки ' here; лямбда-функция Iterator Function ... End Function возвращает IEnumerable(Of Integer), но не является самим таким объектом. Он должен быть вызван для получения этого объекта.

Преобразованный код в [1] вызывает ошибки в C # 7.3 (CS0149):

 static void Main()
{
    Console.Write("x: ");
    var x = System.Convert.ToInt32(Console.ReadLine());
    // ERROR: CS0149 - Method name expected 
    foreach (var elem in () =>
    {
        var i = x;
        do
        {
            yield return i;
            i += 1;
            x -= 1;
        }
        while (!i == x + 20);
    }())
        Console.WriteLine($"{elem} to {x}");
    Console.ReadKey();
}

Я категорически не согласен с причиной, приведенной в других ответах, что компилятору трудно справиться. Iterator Function(), который вы видите в примере VB.NET, специально создан для лямбда-итераторов.

В VB есть ключевое слово Iterator; у него нет аналога C #. ИМХО, нет реальной причины, по которой это не является особенностью C #.

Так что если вы действительно хотите анонимные функции итератора, в настоящее время используйте Visual Basic или (я не проверял) F #, как указано в комментарии Часть № 7 в ответе @Thomas Levesque (сделайте Ctrl + F для F #).

ответил Bolpat 8 +03002018-10-08T18:20:33+03:00312018bEurope/MoscowMon, 08 Oct 2018 18:20:33 +0300 2018, 18:20:33

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

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132