Ответ
Компилятор C# преобразует foreach в низкоуровневый код, используя интерфейсы IEnumerable/IEnumerator. Конкретная реализация зависит от типа коллекции.
1. Для коллекций, реализующих IEnumerable<T> или IEnumerable:
Генерируется код, явно использующий перечислитель с гарантированным освобождением ресурсов через IDisposable.
// Исходный код:
foreach (var item in collection)
{
Console.WriteLine(item);
}
// Примерный код после компиляции:
IEnumerator enumerator = collection.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item); // Тело цикла
}
}
finally
{
if (enumerator is IDisposable disposable)
{
disposable.Dispose(); // Важно для освобождения ресурсов
}
}
2. Для массивов (тип T[]):
Компилятор генерирует более эффективный цикл for с доступом по индексу, минуя накладные расходы на создание перечислителя.
// Исходный код:
foreach (var item in array)
{
Console.WriteLine(item);
}
// Примерный код после компиляции:
for (int i = 0; i < array.Length; i++)
{
var item = array[i];
Console.WriteLine(item); // Тело цикла
}
Ключевые особенности:
- Гарантия очистки: Используется блок
try/finallyдля вызоваDispose()у перечислителя, что критично для таких ресурсов, какIEnumerator, возвращаемый при работе с файлами или сетевыми потоками. - Оптимизация для массивов: Прямой доступ по индексу делает итерацию по массивам максимально быстрой.
- Неизменяемость коллекции: Во время выполнения
foreachисходную коллекцию изменять нельзя, иначе будет выброшено исключениеInvalidOperationException. - Требуемый интерфейс: Цикл работает только с типами, реализующими
IEnumerableили являющимися массивами.