Підрозділ 10.8
Ітератори та yield return
10.8. Ітератори та yield return У розділі 10.7 ми побачили, що для реалізації IEnumerable<T необхідно або написати окремий клас перелічувач IEnumerator<T , або скористатися yield return у тілі GetEnumerator . Д
10.8. Ітератори та yield return
У розділі 10.7 ми побачили, що для реалізації IEnumerable<T> необхідно або написати окремий клас-перелічувач (IEnumerator<T>), або скористатися yield return у тілі GetEnumerator(). Другий варіант — набагато простіший і значно поширеніший на практиці.
yield return — це ключове слово, яке перетворює звичайний метод на ітераторний метод. Компілятор сам генерує клас-перелічувач з усіма необхідними полями та методами. Ви описуєте логіку обходу; C# будує машину стану.
Синтаксис і базовий принцип
Метод вважається ітераторним, якщо:
- Тип повернення —
IEnumerable<T>,IEnumerator<T>або їхні негенерик-варіанти. - Тіло містить хоча б один
yield returnабоyield break.
IEnumerable<int> Sequence()
{
yield return 1;
yield return 2;
yield return 3;
}Кожен виклик yield return повертає один елемент і призупиняє виконання. При наступному виклику MoveNext() виконання відновлюється з тієї ж точки. yield break завершує ітерацію достроково.
Ліниве виконання
Ключова властивість ітераторних методів: тіло методу не виконується в момент виклику. Воно запускається лише тоді, коли foreach або інший споживач запросить перший елемент.

IEnumerable<string> GetActivePatients(List<Patient> all)
{
Console.WriteLine("Початок перебору"); // виконається при першому MoveNext()
foreach (var p in all)
if (p.IsActive)
yield return p.Name; // пауза до наступного MoveNext()
Console.WriteLine("Перебір завершено");
}
// Тут метод ще НЕ виконується:
var seq = GetActivePatients(patients);
// Тут починається фактичне виконання:
foreach (var name in seq)
Console.WriteLine(name);yield return у GetEnumerator — runnable приклад
Найпоширеніший патерн: реалізація IEnumerable<T> без окремого класу-перелічувача. Клас Department фільтрує пацієнтів за статусом прямо в GetEnumerator():
yield return як самостійний метод — runnable приклад
yield return може бути у будь-якому методі з типом повернення IEnumerable<T>, не лише в GetEnumerator(). Це дозволяє будувати ланцюжки фільтрів і генераторів:
Що компілятор генерує з yield return
yield return — це синтаксичний цукор. Компілятор перетворює ітераторний метод на клас-машину стану з полями для всіх локальних змінних та покажчиком поточного кроку:
Ваш код:
IEnumerable<T> MyMethod() { yield return a; yield return b; }
Генерований код (спрощено):
class MyMethod_StateMachine : IEnumerator<T>
{
int _state = 0;
T _current;
bool MoveNext() {
switch (_state) {
case 0: _current = a; _state = 1; return true;
case 1: _current = b; _state = 2; return true;
default: return false;
}
}
T Current => _current;
}Саме тому між двома yield return зберігається стан усіх локальних змінних: вони стають полями класу.
yield return vs явний IEnumerator
| Критерій | yield return | Явний IEnumerator |
|---|---|---|
| Обсяг коду | Мінімальний | Значний (клас зі станом) |
| Читабельність | Висока | Середня |
| Повний контроль | Частковий | Повний (Reset, Dispose) |
| Ліниве виконання | Автоматично | Вручну |
| Коли використовувати | 95% випадків | Складні нестандартні сценарії |
yield return та IEnumerable — підсумок розділу 10
Раздел 10 охоплює повний ланцюжок від конкретних колекцій до абстракцій:
List<T> — індексована, динамічна
LinkedList<T> — двозв'язний список, O(1) вставка
Queue<T> — FIFO-черга
Stack<T> — LIFO-стек
Dictionary<K,V> — хеш-таблиця
ObservableCollection — з подією CollectionChanged
IEnumerable<T> — контракт «можна перебрати foreach»
yield return — генерує IEnumerator<T> без зайвого кодуКожен рівень дає додатковий рівень абстракції: foreach не знає, що за нею стоїть — список у пам'яті, черга у БД чи генератор з yield return. Вона просто викликає MoveNext().