Ответ
Итератор — это паттерн, позволяющий последовательно обходить элементы коллекции, не раскрывая её внутреннее устройство. В 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) не зависнет. Он же на паузе стоит, ждёт команды!