← Назад к вопросам

Во что раскрывается foreach?

2.0 Middle🔥 112 комментариев
#Коллекции и структуры данных#Основы C# и .NET

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Раскрытие цикла foreach в C#

foreach в C# является "синтаксическим сахаром" — удобной конструкцией, которая на этапе компиляции раскрывается в более низкоуровневый код. В зависимости от контекста, компилятор генерирует разные шаблоны.

Основная схема раскрытия (для коллекций, реализующих IEnumerable<T>)

Для большинства стандартных коллекций foreach раскрывается в код, использующий интерфейсы IEnumerable<T> и IEnumerator<T>.

// Исходный код
foreach (var item in collection)
{
    Console.WriteLine(item);
}

// Раскрытая версия (примерный эквивалент)
var enumerator = collection.GetEnumerator();
try
{
    while (enumerator.MoveNext())
    {
        var item = enumerator.Current;
        Console.WriteLine(item);
    }
}
finally
{
    enumerator?.Dispose();
}

Ключевые детали раскрытия

1. Получение энумератора

Компилятор вызывает метод GetEnumerator(). Если коллекция реализует IEnumerable<T>, используется типизированная версия, иначе — нетипизированная.

// Оптимизация: для массивов используется специальная логика
int[] array = new int[10];
// Вместо GetEnumerator() компилятор генерирует доступ по индексу

2. Паттерн и интерфейсы

Цикл работает с любым типом, который реализует паттерн перечислителя:

  • Метод GetEnumerator() (возвращающий объект с методами MoveNext() и Current и IDisposable)
  • ИЛИ интерфейс IEnumerable/IEnumerable<T>

3. Автоматическое освобождение ресурсов

Компилятор оборачивает код в try-finally, гарантируя вызов Dispose() для энумератора. Это критически важно для:

  • Освобождения неуправляемых ресурсов
  • Корректной работы с yield return (итераторы на основе state-машины)
// finally блок гарантирует очистку даже при исключениях
finally
{
    if (enumerator is IDisposable disposable)
        disposable.Dispose();
}

4. Специальная оптимизация для массивов

Для массивов компилятор генерирует более эффективный код на основе индексов:

// Вместо foreach генерируется обычный for-цикл
int[] array = new int[10];
int[] tempArray = array;
for (int i = 0; i < tempArray.Length; i++)
{
    var item = tempArray[i];
    // тело цикла
}

Сравнение с другими конструкциями

ОсобенностьforeachРаскрытая версия
БезопасностьАвтоматический Dispose()Требует явного управления
ПроизводительностьОптимизируется для массивовКонсистентная
ГибкостьТолько чтениеПозволяет модифицировать коллекцию (с осторожностью)
ОтказоустойчивостьЗащита от исключенийТребует ручной обработки

Практические примеры

Пример с пользовательской коллекцией

public class CustomCollection<T> : IEnumerable<T>
{
    private T[] items = new T[10];
    
    public IEnumerator<T> GetEnumerator()
    {
        for (int i = 0; i < items.Length; i++)
            yield return items[i];
    }
    
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

// foreach вызовет метод GetEnumerator() выше

Важные ограничения

В цикле foreach нельзя:

  • Изменять саму коллекцию (добавлять/удалять элементы)
  • Прямо менять элементы значимых типов (кроме использования ref-структур в новых версиях C#)

Эволюция в современных версиях C#

Начиная с C# 7.0, для некоторых коллекций может использоваться ref возвращаемые значения для избегания копирования. В .NET Core и .NET 5+ оптимизированы реализации GetEnumerator() для стандартных коллекций.

Итог: foreach — это безопасная, читаемая абстракция над паттерном итератора, которая автоматически обрабатывает исключения и освобождает ресурсы, сохраняя при этом высокую производительность благодаря оптимизациям компилятора.

Во что раскрывается foreach? | PrepBro