Ответ
Конструкция foreach является синтаксическим сахаром. Компилятор C# раскрывает её в явный код, использующий интерфейсы IEnumerable/IEnumerator. Это не просто макроподстановка, а преобразование, учитывающее освобождение ресурсов (IDisposable).
Базовое раскрытие для коллекций:
// Исходный код разработчика:
foreach (string element in myList)
{
Process(element);
}
// Эквивалентный код, генерируемый компилятором:
IEnumerator<string> enumerator = myList.GetEnumerator();
try
{
// Цикл перемещается по коллекции, пока есть элементы.
while (enumerator.MoveNext())
{
string element = enumerator.Current; // Получение текущего элемента
Process(element); // Тело оригинального цикла
}
}
finally
{
// Обязательная очистка. Проверка на IDisposable безопасна даже для устаревших коллекций.
(enumerator as IDisposable)?.Dispose();
}
Почему это важно понимать:
- Производительность: Для массивов (
T[]) компилятор генерирует оптимизированный циклfor, что быстрее, чем использованиеIEnumerator. - Поведение при ошибках: Вы не можете модифицировать коллекцию внутри
foreach(например,myList.Add(item)), потому что это нарушит состояние внутреннего перечислителя (enumerator), что приведёт кInvalidOperationException. - Управление ресурсами: Блок
finallyгарантирует, что перечислитель будет корректно освобождён, даже если в теле цикла произойдёт исключение. Это особенно важно для перечислителей, работающих с неуправляемыми ресурсами.
Практический вывод: Используя foreach, вы неявно полагаетесь на корректную реализацию IEnumerable и IDisposable в используемой коллекции, что в стандартных типах (List<T>, Dictionary<TKey,TValue>) уже обеспечено.