Ответ
Ключевое слово yield в C# используется для создания итераторов и генераторов последовательностей. Оно позволяет возвращать элементы по одному, «лениво», без необходимости создавать и хранить всю коллекцию в памяти сразу.
Базовый пример: Генерация последовательности чисел:
public static IEnumerable<int> GenerateSequence(int start, int count)
{
for (int i = start; i < start + count; i++)
{
// При каждом вызове MoveNext() цикл выполняется до следующего yield return
yield return i;
}
// yield break; — можно использовать для досрочного завершения последовательности
}
// Использование
foreach (var number in GenerateSequence(5, 3))
{
Console.WriteLine(number); // Выведет: 5, 6, 7
}
Практический пример: Чтение большого файла построчно:
public static IEnumerable<string> ReadLinesLazily(string filePath)
{
using (var reader = new StreamReader(filePath))
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line; // Возвращаем строку и приостанавливаем выполнение
}
}
}
// Это позволяет обрабатывать гигабайтные файлы, не загружая их целиком в память
foreach (var line in ReadLinesLazily("huge_log.txt"))
{
if (line.Contains("ERROR"))
{
ProcessError(line);
}
}
Как это работает и почему это важно:
- Ленивое выполнение (Lazy Evaluation): Метод с
yield returnне выполняется сразу. При вызове он возвращает специальный объект-итератор. Код внутри метода выполняется только при проходе цикломforeachили при явном вызовеMoveNext(). - Состояние сохраняется: Компилятор C# автоматически генерирует класс, который сохраняет состояние метода (значения локальных переменных и позицию в коде) между вызовами
MoveNext(). - Экономия памяти: Это основной паттерн для работы с потоками данных, которые не помещаются в память (Big Data, стриминг).
- Синтаксический сахар:
yieldскрывает сложность ручной реализации интерфейсовIEnumerableиIEnumerator.
Использование с LINQ: Многие методы LINQ (например, Where, Select) сами построены на yield, что делает цепочки запросов эффективными.
var filteredNumbers = GenerateSequence(1, 1000000)
.Where(x => x % 2 == 0) // Ленивая фильтрация
.Take(10); // Берутся только первые 10 четных чисел
// В памяти одновременно находится лишь несколько элементов, а не миллион.