День девяносто первый. #ЗаметкиНаПолях
Выражение yield return. Особенности работы
1. Множественные итерации
Побочным эффектом выражения
1. Когда выполняется строка
2. Этот итератор передаётся в метод
3. Внутри метода
4. Однако все объекты, созданные в методе
5. Когда мы возвращаемся во внешний код, у нас остаётся только ссылка на итератор. Вызывая метод
Поскольку это неочевидное поведение, инструменты вроде Resharper предупреждают вас о множественных итерациях.
2. Отложенное выполнение
Все примеры использования
1. Выбрать все 1000 продуктов
2. Посчитать цену всех 1000 продуктов
3. Выстроить все продукты по возрастанию цены
4. Перевести все цены в доллары
5. Выбрать 5 продуктов с самой высокой ценой
Используя отложенное выполнение, код:
1. Выбирает 10 продуктов
2. Вычисляет цену 10 продуктов
3. Выстраивает из по возрастанию цены
4. Отбирает первые 5 и переводит их цены в доллары
Несмотря на то, что это выдуманный пример, он чётко показывает, как отложенное выполнение может сильно повысить эффективность. Хотя, заметьте, что пример с отложенным выполнением может делать не то, что вы хотите. В примере выше из всех продуктов отбираются не 10 с наиболее высокой ценой, а 10 первых попавшихся. Поэтому нужно быть внимательным в порядке вызова методов LINQ.
Источники:
- https://docs.microsoft.com/ru-ru/dotnet/csharp/language-reference/keywords/yield
- https://www.kenneth-truyers.net/2016/05/12/yield-return-in-c/
Выражение yield return. Особенности работы
1. Множественные итерации
Побочным эффектом выражения
yield return
является то, что множественные вызовы приводят к множественным итерациям:static void Main(string[] args)Попробуйте догадаться, что будет выведено в консоль? Несмотря на то, что интуиция подсказывает результат
{
var invoices = GetInvoices();
DoubleAmounts(invoices);
Console.WriteLine(invoices.First().Amount);
}
class Invoice { public double Amount { get; set; } }
static IEnumerable<Invoice> GetInvoices()
{
for (var i = 1; i < 11; i++)
yield return new Invoice { Amount = i * 10 };
}
static void DoubleAmounts(IEnumerable<Invoice> invoices)
{
foreach (var invoice in invoices)
invoice.Amount = invoice.Amount * 2;
}
20
, выведено будет 10
:1. Когда выполняется строка
var invoices = GetInvoices();
мы не получаем список счетов (Invoice
), мы получаем итератор, создающий объекты Invoice
.2. Этот итератор передаётся в метод
DoubleAmounts
.3. Внутри метода
DoubleAmounts
итератор используется для создания объектов Invoice
и удвоения суммы счёта (свойство Amount
) каждого объекта.4. Однако все объекты, созданные в методе
DoubleAmounts
, выбрасываются, поскольку на них нет ссылок из внешнего кода.5. Когда мы возвращаемся во внешний код, у нас остаётся только ссылка на итератор. Вызывая метод
First
, мы снова просим создать объект Invoice. Это новый объект, поэтому в результате его свойство Amount
будет иметь значение 10
.Поскольку это неочевидное поведение, инструменты вроде Resharper предупреждают вас о множественных итерациях.
2. Отложенное выполнение
Все примеры использования
yield return
имеют общее свойство. Код выполняется только тогда, когда это необходимо. Механизм паузы/возобновления в методе-итераторе делает это возможным. Используя отложенное выполнение, мы можем делать методы проще, иногда быстрее, а иногда и в принципе делать возможным то, что было невозможно ранее. Например, можно сделать бесконечный генератор чисел и выбирать из него только нужное количество. Весь модуль LINQ в C# построен вокруг отложенного исполнения. Вот как оно повышает эффективность кода:var dollarPrices = FetchProducts().Take(10)Допустим, у нас 1000 продуктов. Без отложенного выполнения, методу бы пришлось:
.Select(p => p.CalculatePrice())
.OrderBy(price => price)
.Take(5)
.Select(price => ConvertTo$(price));
1. Выбрать все 1000 продуктов
2. Посчитать цену всех 1000 продуктов
3. Выстроить все продукты по возрастанию цены
4. Перевести все цены в доллары
5. Выбрать 5 продуктов с самой высокой ценой
Используя отложенное выполнение, код:
1. Выбирает 10 продуктов
2. Вычисляет цену 10 продуктов
3. Выстраивает из по возрастанию цены
4. Отбирает первые 5 и переводит их цены в доллары
Несмотря на то, что это выдуманный пример, он чётко показывает, как отложенное выполнение может сильно повысить эффективность. Хотя, заметьте, что пример с отложенным выполнением может делать не то, что вы хотите. В примере выше из всех продуктов отбираются не 10 с наиболее высокой ценой, а 10 первых попавшихся. Поэтому нужно быть внимательным в порядке вызова методов LINQ.
Источники:
- https://docs.microsoft.com/ru-ru/dotnet/csharp/language-reference/keywords/yield
- https://www.kenneth-truyers.net/2016/05/12/yield-return-in-c/