Ответ
Ключевое слово yield return в C# используется для создания итераторов и реализации ленивого вычисления (lazy evaluation). Вот основные подходы и паттерны для их эффективного использования:
1. Ленивая генерация последовательностей
Элементы вычисляются и возвращаются по одному в момент перечисления, а не все сразу. Это экономит память, особенно для больших или потенциально бесконечных последовательностей.
public static IEnumerable<int> GenerateFibonacci(int count) {
int a = 0, b = 1;
for (int i = 0; i < count; i++) {
yield return a;
int temp = a;
a = b;
b = temp + b;
}
}
// Использование: foreach (var num in GenerateFibonacci(10)) { ... }
// Числа вычисляются по мере необходимости в цикле.
2. Создание конвейеров обработки данных (Pipelines)
Можно строить цепочки методов с yield return, где каждый метод выполняет одно преобразование. Данные "протекают" через конвейер по одному элементу.
public static IEnumerable<int> GetNumbers() {
for (int i = 1; i <= 5; i++) yield return i;
}
public static IEnumerable<int> Square(this IEnumerable<int> source) {
foreach (var num in source) yield return num * num;
}
public static IEnumerable<string> Format(this IEnumerable<int> source) {
foreach (var num in source) yield return $"Value: {num}";
}
// Конвейер: Генерация -> Возведение в квадрат -> Форматирование
var result = GetNumbers().Square().Format();
// Каждый элемент проходит всю цепочку перед обработкой следующего.
3. Фильтрация и отложенная фильтрация
Итераторы идеально подходят для реализации фильтров, которые применяются только во время перечисления.
public static IEnumerable<T> WhereLazy<T>(this IEnumerable<T> source, Predicate<T> predicate) {
foreach (var item in source) {
if (predicate(item)) {
yield return item;
}
}
}
// Фильтрация произойдёт только при перечислении результата.
var evenNumbers = GetNumbers().WhereLazy(n => n % 2 == 0);
4. Объединение нескольких последовательностей
С помощью yield return можно легко объединять данные из разных источников.
public static IEnumerable<T> Merge<T>(params IEnumerable<T>[] sequences) {
foreach (var seq in sequences) {
foreach (var item in seq) {
yield return item;
}
}
}
5. Бесконечные последовательности
Итераторы позволяют представлять потенциально бесконечные потоки данных.
public static IEnumerable<int> RandomNumbers(Random rng) {
while (true) {
yield return rng.Next();
}
}
// Важно использовать с методами, которые ограничивают перечисление, например, .Take()
var tenRandomNumbers = RandomNumbers(new Random()).Take(10);
Критически важные замечания
- Повторное выполнение: Итератор выполняет свой код заново при каждом новом перечислении. Если генерация или обработка ресурсоёмки, результат стоит материализовать (
.ToList(),.ToArray()). - Сторонние эффекты: Изменение состояния во время перечисления может привести к неожиданному поведению, если последовательность перечисляется несколько раз.
- Нельзя использовать в try-catch:
yield returnнельзя помещать внутрь блокаtryсcatch. Это ограничение компилятора.
Итог: yield return — мощный инструмент для работы с последовательностями, который позволяет писать эффективный по памяти и выполнению код, особенно полезный для обработки потоков данных, больших коллекций и построения конвейеров.