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

Во что раскладывается цикл foreach в C#?

2.0 Middle🔥 172 комментариев
#Основы C# и .NET

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

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

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

Разложение цикла foreach в C#

Цикл foreach в C# является синтаксическим сахаром, который компилятор раскладывает в низкоуровневую конструкцию с использованием итераторов. Преобразование зависит от типа коллекции, но общий принцип остаётся единым.

Общий механизм преобразования

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

// Исходный код:
foreach (var item in collection)
{
    // Тело цикла
}

// Приближённый эквивалент после компиляции:
var enumerator = collection.GetEnumerator();
try
{
    while (enumerator.MoveNext())
    {
        var item = enumerator.Current;
        // Тело цикла
    }
}
finally
{
    if (enumerator is IDisposable disposable)
    {
        disposable.Dispose();
    }
}

Ключевые компоненты разложения

  1. Получение итератора — вызов метода GetEnumerator().
  2. Итерация по элементам — использование MoveNext() и Current.
  3. Освобождение ресурсов — автоматическое освобождение через IDisposable в блоке finally.

Особенности для различных типов коллекций

Массивы

Для массивов компилятор генерирует оптимизированный цикл for, что обеспечивает максимальную производительность:

// Исходный код:
int[] numbers = { 1, 2, 3 };
foreach (int num in numbers)
{
    Console.WriteLine(num);
}

// Приближённый эквивалент:
for (int i = 0; i < numbers.Length; i++)
{
    int num = numbers[i];
    Console.WriteLine(num);
}

Обобщённые коллекции (List<T>, Dictionary<TKey, TValue>)

Для стандартных коллекций из System.Collections.Generic используется стандартный механизм с итератором, но часто с внутренними оптимизациями:

List<string> list = new List<string> { "a", "b", "c" };
foreach (string s in list)
{
    Console.WriteLine(s);
}

// Эквивалент:
List<string>.Enumerator enumerator = list.GetEnumerator();
try
{
    while (enumerator.MoveNext())
    {
        string s = enumerator.Current;
        Console.WriteLine(s);
    }
}
finally
{
    enumerator.Dispose(); // List<T>.Enumerator реализует IDisposable
}

Пользовательские коллекции

Для собственных коллекций необходимо реализовать интерфейс IEnumerable или IEnumerable<T>:

public class MyCollection<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

Неизменяемость коллекции

Во время выполнения foreach нельзя модифицировать коллекцию (добавлять/удалять элементы), иначе будет выброшено исключение InvalidOperationException.

List<int> numbers = new List<int> { 1, 2, 3 };
foreach (int num in numbers)
{
    numbers.Add(num * 2); // InvalidOperationException
}

Локальные переменные и замыкания

Каждая итерация создаёт новую локальную переменную для элемента, что важно при использовании замыканий:

var actions = new List<Action>();
foreach (int num in new[] { 1, 2, 3 })
{
    actions.Add(() => Console.WriteLine(num));
}
// Все действия выведут 1, 2, 3 соответственно (до C# 5.0 поведение отличалось)

Производительность

  • Для массивов и списков foreach часто столь же производителен, как ручной цикл for.
  • Для других коллекций может быть незначительный оверхед из-за вызова виртуальных методов.
  • Не стоит микрооптимизировать, заменяя foreach на for без профилирования.

Отличия от ручной итерации

Ручная работа с итератором даёт больше контроля, но требует аккуратности:

// Ручная итерация (редкий случай использования)
using (var enumerator = collection.GetEnumerator())
{
    while (enumerator.MoveNext())
    {
        var item = enumerator.Current;
        if (item == null) break; // Ранний выход
        // Обработка элемента
    }
}

Вывод

Цикл foreach в C# — это высокоуровневая абстракция, которая компилятором раскладывается в код с использованием паттерна итератор. Основные преимущества:

  • Безопасность — автоматическое управление ресурсами.
  • Удобство — лаконичный синтаксис.
  • Гибкость — работа с любыми типами, реализующими IEnumerable.
  • Производительность — оптимизации для массивов и стандартных коллекций.

Понимание внутреннего устройства foreach помогает писать более эффективный код и правильно обрабатывать исключительные ситуации при работе с коллекциями.

Во что раскладывается цикл foreach в C#? | PrepBro