Как работает IEnumerable (подробно)?csharp-128

1. Базовая структура интерфейса

Интерфейс IEnumerable<T> является фундаментом LINQ и всех операций с коллекциями в C#. Его определение:

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

public interface IEnumerator<T> : IDisposable, IEnumerator
{
    T Current { get; }
}

2. Ключевые компоненты

2.1. Итерационный паттерн

  • IEnumerable - представляет последовательность
  • IEnumerator - обеспечивает обход элементов
  • Current - текущий элемент
  • MoveNext() - переход к следующему элементу
  • Reset() - сброс итератора (редко используется)

2.2. Ленивое выполнение

var numbers = Enumerable.Range(1, 10); // Не создает элементы сразу
foreach (var n in numbers)             // Создание по требованию
{
    Console.WriteLine(n);
}

3. Детали реализации

3.1. Компилятор преобразует foreach:

foreach (var item in collection)
{
    // Тело цикла
}

Преобразуется в:

var enumerator = collection.GetEnumerator();
try
{
    while (enumerator.MoveNext())
    {
        var item = enumerator.Current;
        // Тело цикла
    }
}
finally
{
    (enumerator as IDisposable)?.Dispose();
}

3.2. Пример кастомной реализации:

public class FibonacciSequence : IEnumerable<long>
{
    public IEnumerator<long> GetEnumerator()
        => new FibonacciEnumerator();

    IEnumerator IEnumerable.GetEnumerator()
        => GetEnumerator();

    private class FibonacciEnumerator : IEnumerator<long>
    {
        private long _current = 1;
        private long _previous = 0;

        public long Current => _current;
        object IEnumerator.Current => Current;

        public bool MoveNext()
        {
            var next = _current + _previous;
            _previous = _current;
            _current = next;
            return true; // Бесконечная последовательность
        }

        public void Reset()
            => (_current, _previous) = (1, 0);

        public void Dispose() { }
    }
}

4. Особенности работы

4.1. Состояние итератора

  • Каждый вызов GetEnumerator() создает новый итератор
  • Итератор хранит текущую позицию в последовательности
  • Повторный обход требует нового итератора

4.2. Исключения при изменении коллекции

var list = new List<int> { 1, 2, 3 };
var enumerator = list.GetEnumerator();
list.Add(4); // Изменяем коллекцию
enumerator.MoveNext(); // InvalidOperationException

4.3. Yield-синтаксис

public IEnumerable<int> GetOddNumbers(int max)
{
    for (int i = 0; i <= max; i++)
    {
        if (i % 2 != 0)
            yield return i; // Состояние сохраняется между вызовами MoveNext()
    }
}

5. Производительность и оптимизации

5.1. Специальные случаи для коллекций:

  • List<T> реализует IEnumerable<T> напрямую
  • Использует структурный энумератор (избегает аллокаций):
public struct Enumerator : IEnumerator<T>
{
    private readonly List<T> _list;
    private int _index;
    // Реализация методов
}

5.2. LINQ и цепочки методов:

var result = collection
    .Where(x => x > 0)  // Не выполняется сразу
    .OrderBy(x => x)    // Не выполняется сразу
    .Take(10);          // Не выполняется сразу

foreach (var item in result) // Выполнение при итерации

Каждый элемент:

  1. Проверка Where
  2. Сортировка (буферизация)
  3. Учет Take

6. Расширенные сценарии

6.1. Асинхронные последовательности

public async IAsyncEnumerable<int> GetDataAsync()
{
    while (true)
    {
        var data = await FetchDataAsync();
        yield return data;
    }
}

await foreach (var item in GetDataAsync())
{
    // Обработка
}

6.2. Кастомные оптимизации:

public class OptimizedEnumerable<T> : IEnumerable<T>
{
    public IEnumerator<T> GetEnumerator()
        => new OptimizedEnumerator();

    // Специальная реализация для Count(), Any() и т.д.
    public int Count() => /* прямое вычисление */;
    public bool Any() => /* проверка без полного перебора */;
}

Резюмируем:

IEnumerable<T> работает через паттерн итератора, обеспечивая ленивое выполнение и эффективный обход коллекций. Реализация включает генераторы (yield), специализированные энумераторы и поддержку LINQ. Понимание его работы критично для эффективной работы с коллекциями в .NET.