Правильное использование «доходности»

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

Какой из следующих двух фрагментов кода является предпочтительным и почему?

Версия 1: Использование доходности доходности

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        foreach (Product product in products)
        {
            yield return product;
        }
    }
}

Версия 2: Вернуть список

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        return products.ToList<Product>();
    }
}
yield привязан к IEnumerable<T> и ему подобным. Это в какой-то мере ленивая оценка
Jaider 21.12.2012 02:29

вот отличный ответ на аналогичный вопрос. stackoverflow.com/questions/15381708/…

Sanjeev Rai 13.06.2013 14:06

Вот хороший пример использования: stackoverflow.com/questions/3392612/…

ValGe 18.02.2014 20:48

Я вижу хороший случай для использования yield return, если код, который выполняет итерацию по результатам GetAllProducts(), дает пользователю возможность преждевременно отменить обработку.

JMD 27.03.2014 20:04

Я нашел эту тему действительно полезной: programmers.stackexchange.com/a/97350/148944

PiotrWolkowski 11.09.2014 18:49
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
926
5
262 327
14
Перейти к ответу Данный вопрос помечен как решенный

Ответы 14

А что насчет этого?

public static IEnumerable<Product> GetAllProducts()
{
    using (AdventureWorksEntities db = new AdventureWorksEntities())
    {
        var products = from product in db.Product
                       select product;

        return products.ToList();
    }
}

Я думаю, это намного чище. Однако у меня нет VS2008 для проверки. В любом случае, если Products реализует IEnumerable (как кажется, он используется в инструкции foreach), я бы вернул его напрямую.

Измените OP, чтобы включить дополнительную информацию вместо публикации ответов.

Brian Rasmussen 04.01.2009 01:56

Что ж, вы должны сказать мне, что именно означает OP :-) Спасибо

petr k. 04.01.2009 02:01

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

petr k. 04.01.2009 02:02

Предполагая, что класс LINQ ваших продуктов использует аналогичный yield для перечисления / итерации, первая версия более эффективна, потому что она дает только одно значение при каждой итерации.

Второй пример - преобразование перечислителя / итератора в список с помощью метода ToList (). Это означает, что он вручную перебирает все элементы в перечислителе, а затем возвращает плоский список.

Ответ принят как подходящий

Я обычно использую yield-return, когда вычисляю следующий элемент в списке (или даже следующую группу элементов).

Используя вашу версию 2, вы должны иметь полный список перед возвращением. Используя yield-return, вам действительно нужно иметь только следующий элемент перед возвратом.

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

Другой случай, когда yield-return предпочтительнее, - это если IEnumerable представляет бесконечное множество. Рассмотрим список простых чисел или бесконечный список случайных чисел. Вы никогда не сможете вернуть полный IEnumerable сразу, поэтому вы используете yield-return для постепенного возврата списка.

В вашем конкретном примере у вас есть полный список продуктов, поэтому я бы использовал версию 2.

Я бы придирался к тому, что в вашем примере в вопросе 3 объединены два преимущества. 1) Он распределяет вычислительные затраты (иногда выгода, иногда нет) 2) Он может лениво избегать вычислений на неопределенный срок во многих случаях использования. Вы не упомянули потенциальный недостаток, который он поддерживает в промежуточном состоянии. Если у вас есть значительное количество промежуточных состояний (скажем, HashSet для устранения дубликатов), то использование yield может увеличить объем памяти.

Kennet Belenky 11.09.2012 21:15

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

Kennet Belenky 11.09.2012 21:26

И, наконец ... есть немного неудобный, но иногда эффективный метод использования yield для написания асинхронного кода в очень сериализованной форме.

Kennet Belenky 11.09.2012 21:27

Другой пример, который может быть интересен, - это чтение довольно больших файлов CSV. Вы хотите прочитать каждый элемент, но вы также хотите удалить свою зависимость. Yield, возвращающий IEnumerable <>, позволит вам возвращать каждую строку и обрабатывать каждую строку отдельно. Не нужно читать файл размером 10 Мбайт в память. Только по одной строке за раз.

Maxime Rouiller 04.01.2013 17:46
Yield return, похоже, является сокращением для написания собственного пользовательского класса итератора (реализуйте IEnumerator). Следовательно, упомянутые преимущества также применимы к пользовательским классам итераторов. В любом случае обе конструкции сохраняют промежуточное состояние. В самой простой форме речь идет о хранении ссылки на текущий объект.
J. Ouwehand 06.09.2016 13:37

Верните список напрямую. Преимущества:

  • Это яснее
  • Список можно использовать повторно. (итератора нет) не совсем правда, спасибо Джон

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

Не забывайте, что он возвращается в IEnumerable <T>, а не в IEnumerator <T> - вы можете снова вызвать GetEnumerator.

Jon Skeet 04.01.2009 02:11

Даже если вы заранее знаете, что весь список нужно будет рассчитать, все же может быть полезно использовать возврат доходности. Один из примеров - коллекция содержит сотни тысяч элементов.

Val 21.06.2017 18:10

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

На этот вопрос нет правильного или неправильного ответа. Какой из них предпочтительнее, зависит от ситуации. Например, если у вас есть ограничение по времени для выполнения вашего запроса, и вам нужно сделать что-то полусложное с результатами, вторая версия может быть предпочтительнее. Но будьте осторожны с большими наборами результатов, особенно если вы запускаете этот код в 32-битном режиме. При использовании этого метода меня несколько раз кусали исключения OutOfMemory.

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

Это вроде как не в тему, но, поскольку вопрос помечен как лучшие практики, я брошу свои два цента. Для этого типа вещей я очень предпочитаю превратить его в собственность:

public static IEnumerable<Product> AllProducts
{
    get {
        using (AdventureWorksEntities db = new AdventureWorksEntities()) {
            var products = from product in db.Product
                           select product;

            return products;
        }
    }
}

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

prices = Whatever.AllProducts.Select (product => product.price);

против

prices = Whatever.GetAllProducts().Select (product => product.price);

Примечание: Я бы не стал делать это для каких-либо методов, работа которых может занять некоторое время.

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

Примечание: не думайте о ключевом слове yield просто как о еще одном способе создания коллекции. Большая часть возможностей yield заключается в том, что исполнение приостановлено в вашем метод или свойство, пока вызывающий код не перебирает следующее значение. Вот мой пример:

Использование ключевого слова yield (наряду с реализацией Caliburn.Micro coroutines Роба Эйзенбурга) позволяет мне выражать асинхронный вызов веб-службы следующим образом:

public IEnumerable<IResult> HandleButtonClick() {
    yield return Show.Busy();

    var loginCall = new LoginResult(wsClient, Username, Password);
    yield return loginCall;
    this.IsLoggedIn = loginCall.Success;

    yield return Show.NotBusy();
}

Это приведет к включению моего BusyIndicator, вызову метода Login в моей веб-службе, установке моего флага IsLoggedIn на возвращаемое значение, а затем снова выключение BusyIndicator.

Вот как это работает: IResult имеет метод Execute и событие Completed. Caliburn.Micro захватывает IEnumerator из вызова HandleButtonClick () и передает его в метод Coroutine.BeginExecute. Метод BeginExecute начинает перебирать результаты IResults. Когда возвращается первый IResult, выполнение приостанавливается внутри HandleButtonClick (), а BeginExecute () присоединяет обработчик события к событию Completed и вызывает Execute (). IResult.Execute () может выполнять как синхронную, так и асинхронную задачу и запускает событие Completed по завершении.

LoginResult выглядит примерно так:

public LoginResult : IResult {
    // Constructor to set private members...

    public void Execute(ActionExecutionContext context) {
        wsClient.LoginCompleted += (sender, e) => {
            this.Success = e.Result;
            Completed(this, new ResultCompletionEventArgs());
        };
        wsClient.Login(username, password);
    }

    public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
    public bool Success { get; private set; }
}

Это может помочь настроить что-то подобное и пошагово выполнить выполнение, чтобы посмотреть, что происходит.

Надеюсь, это кому-то поможет! Мне очень понравилось изучать различные способы использования yield.

ваш образец кода - отличный пример того, как использовать yield OUTSIDE блока for или foreach. Большинство примеров демонстрируют возврат yield в итераторе. Очень полезно, так как я как раз собирался задать вопрос о том, как использовать yield вне итератора!

shelbypereira 15.08.2019 14:12

Мне никогда не приходило в голову использовать yield таким образом. Это кажется элегантным способом эмуляции шаблона async / await (который, как я полагаю, использовался бы вместо yield, если бы он был переписан сегодня). Обнаружили ли вы, что такое творческое использование yield принесло (без каламбура) уменьшающуюся отдачу с годами по мере развития C# с тех пор, как вы ответили на этот вопрос? Или вы все еще придумываете модернизированные умные сценарии использования, подобные этому? И если да, не могли бы вы поделиться еще одним интересным для нас сценарием?

Lopsided 05.03.2020 19:39

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

Если вызывающей стороне этого метода требуется «одна» информация за раз, а следующая информация потребляется по запросу, тогда было бы полезно использовать yield return, который будет гарантировать, что команда выполнения будет возвращена вызывающей стороне, когда единица информации доступна.

Вот несколько примеров использования yield return:

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

Чтобы ответить на ваши вопросы, я бы использовал версию 2.

В качестве концептуального примера для понимания того, когда вам следует использовать yield, предположим, что метод ConsumeLoop() обрабатывает элементы, возвращаемые / выданные ProduceList():

void ConsumeLoop() {
    foreach (Consumable item in ProduceList())        // might have to wait here
        item.Consume();
}

IEnumerable<Consumable> ProduceList() {
    while (KeepProducing())
        yield return ProduceExpensiveConsumable();    // expensive
}

Без yield вызов ProduceList() может занять много времени, потому что вам нужно заполнить список перед возвратом:

//pseudo-assembly
Produce consumable[0]                   // expensive operation, e.g. disk I/O
Produce consumable[1]                   // waiting...
Produce consumable[2]                   // waiting...
Produce consumable[3]                   // completed the consumable list
Consume consumable[0]                   // start consuming
Consume consumable[1]
Consume consumable[2]
Consume consumable[3]

Используя yield, он перестраивается, как бы чередуется:

//pseudo-assembly
Produce consumable[0]
Consume consumable[0]                   // immediately yield & Consume
Produce consumable[1]                   // ConsumeLoop iterates, requesting next item
Consume consumable[1]                   // consume next
Produce consumable[2]
Consume consumable[2]                   // consume next
Produce consumable[3]
Consume consumable[3]                   // consume next

И, наконец, как многие ранее уже предлагали, вам следует использовать версию 2, потому что у вас уже есть полный список.

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

Я прекрасно понимаю, что этот ответ не является техническим ответом, но я считаю, что сходство между yield и потоковой передачей видео служит хорошим примером для понимания ключевого слова yield. Все техническое по этому поводу уже было сказано, поэтому я попытался объяснить «другими словами». Есть ли правило сообщества, согласно которому вы не можете объяснять свои идеи нетехническими терминами?

anar khalilov 08.07.2013 19:00

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

senfo 08.07.2013 19:46

Все еще понимая концепцию, это помогло сфокусироваться на ней, хорошая аналогия.

Tony 21.10.2014 17:28

Мне нравится этот ответ, но он не отвечает на вопрос.

ANeves thinks SE is evil 02.10.2015 17:56

Что ж, аналогия неверна, когда один фильтрует поток (с where) или в конечном итоге обрабатывает его (с select, не являющимся select(o => o)).

Soleil 14.09.2020 16:06

У Yield есть два важных применения

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

Это помогает выполнять итерацию с отслеживанием состояния. (потоковое)

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

https://thewikihow.com/video_4fju3xcm21M

Это то, что Крис Селлс сообщает об этих операторах в Язык программирования C#;

I sometimes forget that yield return is not the same as return , in that the code after a yield return can be executed. For example, the code after the first return here can never be executed:

int F() {
    return 1;
    return 2; // Can never be executed
}

In contrast, the code after the first yield return here can be executed:

IEnumerable<int> F() {
    yield return 1;
    yield return 2; // Can be executed
}

This often bites me in an if statement:

IEnumerable<int> F() {
    if (...) {
        yield return 1; // I mean this to be the only thing returned
    }
    yield return 2; // Oops!
}

In these cases, remembering that yield return is not “final” like return is helpful.

чтобы уменьшить двусмысленность, пожалуйста, поясните, когда вы говорите «могу», будет ли это, будет или возможно? возможно ли, чтобы первый вернулся и не выполнил второй yield?

Johno Crawford 07.02.2019 18:42

@JohnoCrawford, второй оператор yield будет выполняться только в том случае, если перечислено второе / следующее значение IEnumerable. Вполне возможно, что это не так, например F().Any() - это вернется после попытки перечислить только первый результат. В общем, вам не следует полагаться на IEnumerable yield для изменения состояния программы, потому что он может не сработать.

Zac Faragher 12.07.2019 02:49

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

    static IEnumerable<Trip> CreatePossibleTrips()
    {
        for (int i = 0; i < 1000000; i++)
        {
            yield return new Trip
            {
                Id = i.ToString(),
                Driver = new Driver { Id = i.ToString() }
            };
        }
    }

Затем повторите каждую поездку:

    static void Main(string[] args)
    {
        foreach (var trip in CreatePossibleTrips())
        {
            // possible trip is actually calculated only at this point, because of yield
            if (IsTripGood(trip))
            {
                // match good trip
            }
        }
    }

Если вы используете List вместо yield, вам нужно будет выделить в память 1 миллион объектов (~ 190 МБ), и для выполнения этого простого примера потребуется ~ 1400 мсек. Однако, если вы используете yield, вам не нужно помещать все эти временные объекты в память, и вы получите значительно более высокую скорость алгоритма: этот пример займет всего ~ 400 мсек без потребления памяти вообще.

под одеялом какая урожайность? Я бы подумал, что это список, поэтому как он может улучшить использование памяти?

rolls 07.05.2017 05:52

@rolls yield работает под прикрытием, внутренне реализуя конечный автомат. Вот ответ SO с 3 подробными сообщениями в блоге MSDN, которые очень подробно объясняют реализацию. Автор Раймонд Чен @ MSFT

Shiva 24.02.2018 00:49

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

урожай имеет два преимущества:

  1. Вам не нужно читать эти значения дважды;
  2. Вы можете получить много дочерних узлов, но не должны помещать их все в память.

Есть еще один четкий объяснение, возможно, вам поможет.

Другие вопросы по теме