Приведи пример использования ключевого слова yield в C#

«Приведи пример использования ключевого слова yield в C#» — вопрос из категории C# Core, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Ключевое слово 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 четных чисел
// В памяти одновременно находится лишь несколько элементов, а не миллион.