Что такое итератор и ключевое слово yield в C#?

Ответ

Итератор — это паттерн, позволяющий последовательно обходить элементы коллекции, не раскрывая её внутреннее устройство. В C# итераторы реализуются через интерфейсы IEnumerable (предоставляет перечислитель) и IEnumerator (непосредственно выполняет перебор).

Ключевое слово yield — это синтаксический сахар, который значительно упрощает создание итераторов. Компилятор автоматически генерирует класс, реализующий IEnumerable/IEnumerator, с сохранением состояния между шагами.

Основные понятия:

  • yield return — возвращает следующий элемент последовательности и приостанавливает выполнение метода до следующего вызова MoveNext().
  • yield break — завершает последовательность.
  • Ленивое выполнение: Элементы вычисляются только в момент запроса, что экономит память и CPU.

Пример: Генерация последовательности и фильтрация "на лету"

// Итератор, генерирующий бесконечную последовательность чисел Фибоначчи
public static IEnumerable<long> FibonacciSequence()
{
    long a = 0, b = 1;
    while (true)
    {
        yield return a; // Приостановка и возврат значения
        long temp = a;
        a = b;
        b = temp + b;
    }
}

// Итератор для фильтрации (пайплайн)
public static IEnumerable<int> GetEvenNumbers(IEnumerable<int> source)
{
    foreach (int number in source)
    {
        if (number % 2 == 0)
        {
            yield return number; // Возвращаем только чётные
        }
        // Если число нечётное, просто пропускаем итерацию
    }
}

// Использование:
// 1. Берём первые 10 чисел Фибоначчи
var firstTenFib = FibonacciSequence().Take(10);
// 2. Фильтруем их, оставляя только чётные
var evenFib = GetEvenNumbers(firstTenFib);
// 3. Материализуем результат (здесь происходит реальное вычисление)
foreach (var num in evenFib)
{
    Console.WriteLine(num); // Выведет: 0, 2, 8, 34...
}

Важные ограничения:

  • Методы с yield не могут содержать return (кроме yield return/break).
  • yield return нельзя использовать внутри try-catch, но можно внутри try-finally (финальная часть выполнится при завершении перебора).

Ответ 18+ 🔞

Давай разберём эту тему, а то многие её либо не понимают, либо боятся, как чёрт ладана. Итератор — это, по сути, твой личный зануда-помощник, который умеет ходить по коллекции от первого элемента к последнему, не заглядывая ей под капот. Ему похуй, что там внутри — массив, список или связный список из ёжиков и ужей. Его задача — тыкать пальцем в следующий элемент и говорить: «Вот, держи, следующий!».

В C# за это отвечают два интерфейса: IEnumerable (это тот, кто говорит «я умею выдавать перечислитель») и IEnumerator (это уже сам перечислитель, который ползает и тыкает). Писать их вручную — это такой же геморрой, как собирать мебель из Икеи без инструкции. Поэтому умные дядьки придумали волшебное слово yield.

yield — это, блядь, магия чистой воды. Ты пишешь метод, который возвращает IEnumerable, внутри него в цикле делаешь yield return какой-то_элемент, и компилятор, ёпта, сам генерирует за тебя весь этот адский boilerplate-код с сохранением состояния между шагами. Выглядит как обычный метод, а работает как итератор. Красота!

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

  • yield return — возвращает элемент и ставит метод на паузу, ровно до следующего вызова MoveNext(). Это не return в привычном смысле, это «на, держи, и жди следующей команды».
  • yield break — это стоп-кран. Всё, конец последовательности, расходимся.
  • Ленивость (lazy evaluation) — вот это вообще пиздец как важно. Ничего не вычисляется заранее. Твой итератор сидит, курит бамбук, пока ты не начнёшь реально перебирать элементы в foreach. Экономия памяти и процессора — просто овердохуища, особенно на больших или бесконечных последовательностях.

Смотри, как это в жизни выглядит:

// Генератор чисел Фибоначчи. Бесконечный, как твои проблемы.
public static IEnumerable<long> FibonacciSequence()
{
    long a = 0, b = 1;
    while (true) // Да, тут бесконечный цикл, и это нормально!
    {
        yield return a; // Отдаём число и засыпаем
        long temp = a;
        a = b;
        b = temp + b;
    }
}

// Фильтр. Пропускает только чётные числа. Остальные — в помойку.
public static IEnumerable<int> GetEvenNumbers(IEnumerable<int> source)
{
    foreach (int number in source)
    {
        if (number % 2 == 0)
        {
            yield return number; // Чётное? Бери!
        }
        // Нечётное? Да похуй, просто идём дальше.
    }
}

// А теперь используем эту красоту:
// 1. Хотим первые 10 чисел Фибоначчи
var firstTenFib = FibonacciSequence().Take(10);
// 2. Из них отфильтруем только чётные
var evenFib = GetEvenNumbers(firstTenFib);
// 3. ВНИМАНИЕ! Вычисления начнутся ТОЛЬКО здесь, в foreach!
foreach (var num in evenFib)
{
    Console.WriteLine(num); // Выведет: 0, 2, 8, 34...
}

Видишь магию? Мы создали две ленивые последовательности. Ни одно число Фибоначчи не было вычислено, пока мы не дошли до foreach. А потом оно посчитало ровно столько, сколько нужно, и сразу отфильтровалось. Эффективность, блядь, на уровне!

Но есть и подводные камни, куда ж без них:

  • В методе с yield нельзя использовать обычный return (только yield return и yield break).
  • yield return нельзя засунуть в блок try-catch (компилятор ругнется, как сука). Но в try-finally — можно. finally-блок выполнится, когда перебор закончится (или сломается), что иногда очень полезно для очистки ресурсов.

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