Коллекция вопросов ❓ на собеседовании в C# 🍧

Обзор вопросов по языка програмиированию C# и технологии NET


Project maintained by Dvurechensky-Docs Hosted on GitHub Pages — Theme by mattgraham

🔄 Что такое итераторы и как они реализуются?

Typing SVG

Static Badge

✨ Оглавление

⬆ Вернуться к главной


1️⃣ Основная идея итераторов


2️⃣ Интерфейсы

IEnumerable<T>

public interface IEnumerable<out T>
{
    IEnumerator<T> GetEnumerator();
}

IEnumerator<T>

public interface IEnumerator<out T> : IDisposable
{
    T Current { get; }
    bool MoveNext();
    void Reset(); // редко используется
}

3️⃣ Реализация через yield return

IEnumerable<int> GetNumbers()
{
    for (int i = 0; i < 5; i++)
        yield return i;
}

4️⃣ Deferred execution

var numbers = GetNumbers(); // метод ещё не выполняется
foreach(var n in numbers)    // тут начинается генерация
    Console.WriteLine(n);

5️⃣ yield break

IEnumerable<int> NumbersUpTo(int max)
{
    for (int i = 0; i < max; i++)
    {
        if (i == 3)
            yield break; // завершение итератора
        yield return i;
    }
}

6️⃣ Сложные моменты и каверзные вопросы

  1. Deferred vs Immediate

    • yield return → deferred.
    • ToList() → immediate.
  2. Многоэтапные итераторы

    • Можно использовать несколько yield return в условных блоках, каждый поддерживает своё состояние.
  3. Изменение коллекции во время итерации

    • Может вызвать InvalidOperationException (для большинства коллекций, кроме Concurrent).
  4. Lazy evaluation

    • Итераторы помогают реализовать ленивое вычисление (lazy).
  5. IEnumerator.Dispose()

    • Автоматически вызывается после окончания перебора (foreach использует try-finally).
  6. Структурные итераторы

    • Можно возвращать struct для уменьшения накладных расходов на GC.
  7. Несколько foreach на одном IEnumerable

    • Каждый вызов GetEnumerator создаёт новый независимый перебор.

7️⃣ Пример сложного итератора

IEnumerable<int> FilterAndSquare(IEnumerable<int> numbers)
{
    foreach (var n in numbers)
    {
        if (n % 2 == 0)
            yield return n * n; // только чётные возводим в квадрат
    }
}

8️⃣ Итераторы и LINQ

var evens = numbers.Where(n => n % 2 == 0); // ещё не выполняется
foreach(var n in evens)
    Console.WriteLine(n); // здесь выполнение

9️⃣ Подводные моменты для собеса

  1. yield return vs обычный List

    • Меньше памяти, lazy, но нельзя использовать с асинхронными методами напрямую.
  2. IEnumerator vs IEnumerable

    • IEnumerable предоставляет enumerator, который делает фактический перебор.
  3. Можно ли использовать yield return в асинхронных методах?

    • Нет, для этого есть IAsyncEnumerable<T> и await foreach.
  4. Как устроен внутренний state machine компилятора для yield return?

    • Каждый yield → точка сохранения состояния.
  5. Почему foreach вызывает Dispose?

    • Для правильного освобождения ресурсов в IEnumerator.

🔟 Таблица сравнения

Характеристика IEnumerable IEnumerator yield return
Кто предоставляет Коллекция Перебор Компилятор создаёт enumerator
Deferred execution Да Нет Да
Dispose Нет Да Да (через enumerator)
Много переборов Да Один Да, каждый GetEnumerator новый
Сложность реализации Нужно вручную Нужно вручную Очень просто, компилятор генерирует класс

⬆ Вернуться к главной

✨Dvurechensky✨