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

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

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

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

public static IEnumerable 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 GetAllProducts() { using (AdventureWorksEntities db = new AdventureWorksEntities()) { var products = from product in db.Product select product; return products.ToList(); } } 

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

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

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

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

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

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

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

     void ConsumeLoop() { foreach (Consumable item in ProduceList()) // might have to wait here item.Consume(); } IEnumerable ProduceList() { while (KeepProducing()) yield return ProduceExpensiveConsumable(); // expensive } 

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

     //pseudo-assembly Produce consumable[0] // expensive operation, eg 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 Consume Produce consumable[1] Consume consumable[1] // consume next Produce consumable[2] Consume consumable[2] // consume next Produce consumable[3] Consume consumable[3] // consume next 

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

    Это будет похоже на странное предложение, но я узнал, как использовать ключевое слово yield в C #, прочитав презентацию о генераторах в Python: http://www.dabeaz.com/generators/Generators.pdf Дэвида М. Бейзли. Вам не нужно много знать Python, чтобы понять презентацию – я этого не сделал. Мне было очень полезно объяснить не только то, как работают генераторы, но и почему вы должны заботиться.

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

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

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

     public IEnumerable 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 Completed = delegate { }; public bool Success { get; private set; } } 

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

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

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

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

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

    Доходность имеет два больших использования

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

    Это помогает делать итерацию с сохранением состояния. (streamовая передача)

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

    http://www.youtube.com/watch?v=4fju3xcm21M

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

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

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

    Напротив, код после первого возврата возврата здесь может быть выполнен:

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

    Это часто укусит меня в выражении if:

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

    В этих случаях, помня, что возврат доходности не является «окончательным», как возврат полезен.

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

      static IEnumerable 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(trips)) { // possible trip is actually calculated only at this point, because of yield if (IsTripGood(trip)) { // match good trip } } } 

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

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

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

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

     public static IEnumerable 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); 

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

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

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

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

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

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

    Некоторые примеры, в которых можно было бы использовать возврат доходности:

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

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

    Верните список напрямую. Выгоды:

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

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

    Ключевая фраза возврата доходности используется для поддержки конечного автомата для конкретной коллекции. Везде, где CLR видит ключевую фразу yield return, CLR реализует шаблон Enumerator для этого fragmentа кода. Этот тип реализации помогает разработчику от всех видов сантехники, которые нам в противном случае пришлось бы делать в отсутствие ключевого слова.

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

    Подробнее о ключевом задании здесь в этой статье .

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

    доходность имеет два преимущества:

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

    Возможно, вам поможет еще одно ясное объяснение .

    Interesting Posts

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

    Горячее развертывание на JBoss – как мне заставить JBoss «видеть» изменение?

    Получение последней записи в каждой группе – MySQL

    Как я могу объявить внутренний class?

    Почему нет подключенного диска, доступного под расширенным приглашением cmd, но находится под обычной подсказкой cmd?

    Есть ли более, более UNIX-подобная оболочка командной строки для Windows?

    Операция Не разрешена, когда на корне – El Capitan (без корней)

    Технически какая разница между s3n, s3a и s3?

    Предотвращение «сохранения сна» из окон и перезапись разделяемого раздела

    Вопросы, на которые должен отвечать каждый хороший разработчик .NET?

    NSXMLParser на iPhone, как я его использую, учитывая xml-файл (newb здесь: \)

    Передача массива функции по значению

    Ошибка при подписании: SignTool.exe не найден

    Настройка TIME_WAIT TCP

    Что восстанавливает и восстанавливает Windows System Restore?

    Давайте будем гением компьютера.