Что делает оператор yield?csharp-35

Что делает yield?

yield — это контекстное ключевое слово в C#, которое используется для создания итераторов и ленивого выполнения (lazy evaluation) последовательностей. Оно позволяет генерировать элементы коллекции по требованию, а не все сразу.

Основные формы использования

yield return <expression>; // Возвращает очередной элемент последовательности
yield break;             // Завершает последовательность

Как это работает?

Когда метод содержит оператор yield, компилятор автоматически преобразует его в машину состояний, которая:

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

Пример простого итератора

public static IEnumerable<int> GetNumbers(int max)
{
    for (int i = 0; i < max; i++)
    {
        yield return i; // Возвращаем число и "запоминаем" позицию
    }
}

// Использование
foreach (var num in GetNumbers(5))
{
    Console.WriteLine(num); // Выводит 0, 1, 2, 3, 4
}

Ключевые особенности

  1. Ленивое выполнение: Элементы генерируются только когда они запрашиваются
  2. Состояние сохраняется между вызовами
  3. Не требует создания коллекции целиком в памяти

Преимущества перед обычными коллекциями

  1. Экономия памяти: Не нужно хранить всю коллекцию
  2. Бесконечные последовательности: Можно генерировать бесконечные потоки данных
  3. Раннее прерывание: Можно остановить перебор до конца последовательности

Пример с yield break

public static IEnumerable<string> GetUntilNull(IEnumerable<string> input)
{
    foreach (var item in input)
    {
        if (item == null)
            yield break; // Прерываем последовательность

        yield return item;
    }
}

Что происходит под капотом?

Компилятор преобразует метод с yield в класс-итератор, реализующий IEnumerator<T>. Для примера выше создается примерно такой код:

private sealed class <GetNumbers>d__0 : IEnumerable<int>, IEnumerator<int>
{
    private int <>1__state;
    private int <>2__current;
    private int <>3__max;
    private int <i>5__1;

    // Реализация методов MoveNext, Current и т.д.
}

Практические примеры использования

  1. Чтение большого файла построчно без загрузки в память целиком
  2. Генерация последовательностей (числа Фибоначчи, простые числа)
  3. Фильтрация и преобразование данных в потоковом режиме

Ограничения

  1. Нельзя использовать в:
    • Методах с параметрами ref или out
    • Лямбда-выражениях
    • Блоках try-catch, только try-finally
  2. Не поддерживает откат (reset) итерации

Пример бесконечной последовательности

public static IEnumerable<int> Fibonacci()
{
    int a = 0, b = 1;
    while (true)
    {
        yield return a;
        (a, b) = (b, a + b);
    }
}

// Использование (с ограничением)
foreach (var num in Fibonacci().Take(10))
{
    Console.WriteLine(num);
}

Yield в комбинации с LINQ

yield часто используется при реализации собственных LINQ-операторов:

public static IEnumerable<T> Where<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
    foreach (var item in source)
    {
        if (predicate(item))
            yield return item;
    }
}

Резюмируем:


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