День восемьдесят седьмой. #ЗаметкиНаПолях

Выражение yield return. Начало

Выражение yield return, вероятно, одна из наименее известных функций в C#. Несмотря на то, что большинство разработчиков о нём слышали, это выражение часто понимается неправильно. Использование ключевого слова yield в методе означает, что этот метод, оператор или акцессор get является итератором. Использование yield исключает необходимость применения явного дополнительного класса для хранения перечисления. Примеры использования:

yield return <expression>; // возвращает каждый элемент по одному 

yield break; // завершает итерацию

Метод-итератор используется путем применения оператора foreach или запроса LINQ. Каждая итерация цикла foreach вызывает метод-итератор. При достижении в методе-итераторе оператора yield return возвращается <expression> и сохраняется текущее расположение в коде. При следующем вызове метода-итератора выполнение возобновляется с этого места.

Небольшой пример, не имеющий особого смысла, но полезный для отладки и понимания работы yield return:

IEnumerable GetNumbers() { 

yield return 1;

yield return 2;

yield return 3;

}

Вызовем его:

foreach(var number in GetNumbers())

Console.WriteLine(number);

Когда вы пройдёте его по шагам в отладке, вы увидите, что текущая строка выполнения прыгает между циклом foreach и выражениями yield return. То есть каждая итерация цикла вызывает метод-итератор GetNumbers до очередного выражения yield return. Значение возвращается вызывающему коду, а позиция в методе-итераторе сохраняется. Выполнение возобновляется с этой позиции при следующем вызове метода-итератора. Это продолжается, пока не будут вызваны все выражения yield return, либо не встретится yield break.



Yield return против обычных циклов

Вот другой пример. Здесь обычный цикл, возвращающий список:

IEnumerable<int> GenerateWithoutYield()

{

var i = 0;

var list = new List<int>();

while (i<5)

list.Add(++i);

return list;

}

foreach(var number in GenerateWithoutYield())

Console.WriteLine(number)
;

Выполнение этого кода по шагам:

1. Вызывается GenerateWithoutYield.

2. Выполняется весь метод и создаётся список.

3. Цикл foreach проходит по всем значениям списка.

4. В результате числа от 1 до 5 выводятся в консоль.



Аналогичный метод с помощью yield return:

IEnumerable<int> GenerateWithYield()

{

var i = 0;

while (i<5)

yield return ++i;

}

foreach(var number in GenerateWithYield())

Console.WriteLine(number);

На первый взгляд этот метод тоже возвращает список из 5 чисел. Но из-за выражения yield код выполняется совершенно по-другому. Метод вообще не возвращает списка. Он создаёт итератор с обещанием вернуть 5 чисел. Несмотря на то, что результат тот же, есть некоторые нюансы выполнения:

1. Вызывается GenerateWithYield.

2. Тип возвращаемого значения этого метода IEnumerable, но это не список, а обещание вернуть последовательность чисел при запросе. Точнее создаётся итератор, позволяющий выполнить это обещание.

3. В каждой итерации цикла foreach вызывается метод-итератор. При достижении выражения yield return возвращается значение, а текущее положение запоминается. Выполнение продолжается с этого места при следующем вызове метода-итератора.

4. В результате в консоли выводятся числа от 1 до 5.



Источники:

-
https://docs.microsoft.com/ru-ru/dotnet/csharp/language-reference/keywords/yield

-
https://www.kenneth-truyers.net/2016/05/12/yield-return-in-c/



Продолжение следует…