День сто девяносто пятый. #ИнтересныйКод

Ленивый итератор с помощью yield return

Следующий код показывает метод Fibonacci(), возвращающий бесконечную последовательность чисел Фибоначчи, и использование этого метода для вывода чисел до нужного предела:

static IEnumerable<int> Fibonacci()

{

int current = 0;

int next = 1;

// бесконечный цикл? Только если продолжать запрашивать значения

while (true)

{

yield return current;

int oldCurrent = current;

current = next;

next += oldCurrent;

}

}

static void Main()

{

foreach (var value in Fibonacci())

{

Console.WriteLine(value);

// условие остановки цикла

if (value > 1000) { break; }

}

}

Как реализовать что-то подобное без итераторов? Можно изменить метод для создания List<int> и заполнять его, пока не достигнут предел. Но этот список может быть большим, если предел велик, и почему метод, который знает детали последовательности Фибоначчи, также должен знать, когда остановиться? Предположим, иногда нужно остановиться по значению текущего числа, а иногда по количеству выведенных чисел или через некоторое время.

Можно избежать создания списка, печатая значение в цикле, но это делает метод Fibonacci() еще более тесно связанным с тем, что вы хотите сделать со значениями прямо сейчас. Что если вы хотите складывать значения, а не печатать их? Писать другой метод? Это ужасное нарушение разделения ответственности.

Решение через итератор - это именно то, что вам нужно: представление бесконечной последовательности и всё. Вызывающий код может выполнять итерацию по своему усмотрению (по крайней мере, до переполнения int) и использовать значения, как хочет.

Реализация последовательности Фибоначчи вручную проста. Не нужно сохранять много данных состояния (только два предыдущих числа), а управлять логикой выполнения довольно просто (здесь только один оператор yield return). Но если логика усложняется, реализация её в коде становится крайне непростой. Таким образом, использование итераторов с yield return – это мощный инструмент, сильно упрощающий разработку.

Кроме того, компилятор также достаточно умён, чтобы правильно обрабатывать блоки finally, что не так очевидно, как кажется (об этом далее).



Источник: Jon Skeet “C# In Depth”. 4th ed – Manning Publications Co, 2019. Глава 2.