Ключевое слово урожай - одно из тех ключевые слова в 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>();
}
}
вот отличный ответ на аналогичный вопрос. stackoverflow.com/questions/15381708/…
Вот хороший пример использования: stackoverflow.com/questions/3392612/…
Я вижу хороший случай для использования yield return, если код, который выполняет итерацию по результатам GetAllProducts(), дает пользователю возможность преждевременно отменить обработку.
Я нашел эту тему действительно полезной: programmers.stackexchange.com/a/97350/148944





А что насчет этого?
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, чтобы включить дополнительную информацию вместо публикации ответов.
Что ж, вы должны сказать мне, что именно означает OP :-) Спасибо
Исходный пост, я полагаю. Я не могу редактировать сообщения, так что, похоже, это правильный путь.
Предполагая, что класс LINQ ваших продуктов использует аналогичный yield для перечисления / итерации, первая версия более эффективна, потому что она дает только одно значение при каждой итерации.
Второй пример - преобразование перечислителя / итератора в список с помощью метода ToList (). Это означает, что он вручную перебирает все элементы в перечислителе, а затем возвращает плоский список.
Я обычно использую yield-return, когда вычисляю следующий элемент в списке (или даже следующую группу элементов).
Используя вашу версию 2, вы должны иметь полный список перед возвращением. Используя yield-return, вам действительно нужно иметь только следующий элемент перед возвратом.
Помимо прочего, это помогает распределить вычислительные затраты на сложные вычисления на более длительный период времени. Например, если список подключен к графическому интерфейсу пользователя, и пользователь никогда не переходит на последнюю страницу, вы никогда не вычисляете последние элементы в списке.
Другой случай, когда yield-return предпочтительнее, - это если IEnumerable представляет бесконечное множество. Рассмотрим список простых чисел или бесконечный список случайных чисел. Вы никогда не сможете вернуть полный IEnumerable сразу, поэтому вы используете yield-return для постепенного возврата списка.
В вашем конкретном примере у вас есть полный список продуктов, поэтому я бы использовал версию 2.
Я бы придирался к тому, что в вашем примере в вопросе 3 объединены два преимущества. 1) Он распределяет вычислительные затраты (иногда выгода, иногда нет) 2) Он может лениво избегать вычислений на неопределенный срок во многих случаях использования. Вы не упомянули потенциальный недостаток, который он поддерживает в промежуточном состоянии. Если у вас есть значительное количество промежуточных состояний (скажем, HashSet для устранения дубликатов), то использование yield может увеличить объем памяти.
Кроме того, если каждый отдельный элемент очень велик, но к ним нужно обращаться только последовательно, доходность будет лучше.
И, наконец ... есть немного неудобный, но иногда эффективный метод использования yield для написания асинхронного кода в очень сериализованной форме.
Другой пример, который может быть интересен, - это чтение довольно больших файлов CSV. Вы хотите прочитать каждый элемент, но вы также хотите удалить свою зависимость. Yield, возвращающий IEnumerable <>, позволит вам возвращать каждую строку и обрабатывать каждую строку отдельно. Не нужно читать файл размером 10 Мбайт в память. Только по одной строке за раз.
Yield return, похоже, является сокращением для написания собственного пользовательского класса итератора (реализуйте IEnumerator). Следовательно, упомянутые преимущества также применимы к пользовательским классам итераторов. В любом случае обе конструкции сохраняют промежуточное состояние. В самой простой форме речь идет о хранении ссылки на текущий объект.
Верните список напрямую. Преимущества:
Вам следует использовать итератор (yield), когда вы думаете, что вам, вероятно, не придется выполнять итерацию до конца списка, или когда у него нет конца. Например, вызывающий клиент будет искать первый продукт, удовлетворяющий некоторому предикату, вы можете рассмотреть возможность использования итератора, хотя это надуманный пример, и, вероятно, есть более эффективные способы его выполнения. В принципе, если вы заранее знаете, что нужно будет рассчитать весь список, просто сделайте это заранее. Если вы думаете, что это не так, подумайте об использовании версии с итератором.
Не забывайте, что он возвращается в IEnumerable <T>, а не в IEnumerator <T> - вы можете снова вызвать GetEnumerator.
Даже если вы заранее знаете, что весь список нужно будет рассчитать, все же может быть полезно использовать возврат доходности. Один из примеров - коллекция содержит сотни тысяч элементов.
Эти два фрагмента кода действительно делают две разные вещи. Первая версия будет тянуть участников по мере необходимости. Вторая версия загрузит все результаты в память перед, с которой вы начинаете что-либо делать.
На этот вопрос нет правильного или неправильного ответа. Какой из них предпочтительнее, зависит от ситуации. Например, если у вас есть ограничение по времени для выполнения вашего запроса, и вам нужно сделать что-то полусложное с результатами, вторая версия может быть предпочтительнее. Но будьте осторожны с большими наборами результатов, особенно если вы запускаете этот код в 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 вне итератора!
Мне никогда не приходило в голову использовать yield таким образом. Это кажется элегантным способом эмуляции шаблона async / await (который, как я полагаю, использовался бы вместо yield, если бы он был переписан сегодня). Обнаружили ли вы, что такое творческое использование yield принесло (без каламбура) уменьшающуюся отдачу с годами по мере развития C# с тех пор, как вы ответили на этот вопрос? Или вы все еще придумываете модернизированные умные сценарии использования, подобные этому? И если да, не могли бы вы поделиться еще одним интересным для нас сценарием?
В этом случае я бы использовал версию 2 кода. Поскольку у вас есть полный список доступных продуктов, и это то, что ожидает «потребитель» от вызова этого метода, потребуется отправить полную информацию обратно вызывающей стороне.
Если вызывающей стороне этого метода требуется «одна» информация за раз, а следующая информация потребляется по запросу, тогда было бы полезно использовать yield return, который будет гарантировать, что команда выполнения будет возвращена вызывающей стороне, когда единица информации доступна.
Вот несколько примеров использования yield return:
Чтобы ответить на ваши вопросы, я бы использовал версию 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. Все техническое по этому поводу уже было сказано, поэтому я попытался объяснить «другими словами». Есть ли правило сообщества, согласно которому вы не можете объяснять свои идеи нетехническими терминами?
Я не уверен, кто проголосовал против вас и почему (хотелось бы, чтобы они прокомментировали), но я думаю, что это в некоторой степени описывает это с нетехнической точки зрения.
Все еще понимая концепцию, это помогло сфокусироваться на ней, хорошая аналогия.
Мне нравится этот ответ, но он не отвечает на вопрос.
Что ж, аналогия неверна, когда один фильтрует поток (с where) или в конечном итоге обрабатывает его (с select, не являющимся select(o => o)).
У Yield есть два важных применения
Это помогает обеспечить настраиваемую итерацию без создания временных коллекций. (загрузка всех данных и цикл)
Это помогает выполнять итерацию с отслеживанием состояния. (потоковое)
Ниже приведено простое видео, которое я создал с полной демонстрацией, чтобы подтвердить два вышеупомянутых пункта.
Это то, что Крис Селлс сообщает об этих операторах в Язык программирования 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?
@JohnoCrawford, второй оператор yield будет выполняться только в том случае, если перечислено второе / следующее значение IEnumerable. Вполне возможно, что это не так, например F().Any() - это вернется после попытки перечислить только первый результат. В общем, вам не следует полагаться на IEnumerable yield для изменения состояния программы, потому что он может не сработать.
Возврат доходности может быть очень эффективным для алгоритмов, в которых нужно перебирать миллионы объектов. Рассмотрим следующий пример, в котором вам нужно рассчитать возможные поездки для совместного использования. Сначала генерируем возможные поездки:
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 yield работает под прикрытием, внутренне реализуя конечный автомат. Вот ответ SO с 3 подробными сообщениями в блоге MSDN, которые очень подробно объясняют реализацию. Автор Раймонд Чен @ MSFT
Использование урожай аналогично ключевому слову возвращаться, за исключением того, что оно вернет генератор. И объект генератор будет проходить только однажды.
урожай имеет два преимущества:
Есть еще один четкий объяснение, возможно, вам поможет.
yieldпривязан кIEnumerable<T>и ему подобным. Это в какой-то мере ленивая оценка