| layout | default | |||
|---|---|---|---|---|
| title | 🔁 Как работает цикл `for` и чем он отличается от `foreach`? | |||
| description | ||||
| author | Dvurechensky | |||
| date | 2025-08-28 | |||
| published | true | |||
| tags |
|
- ✨ Оглавление
for (int i = 0; i < collection.Length; i++)
{
Console.WriteLine(collection[i]);
}-
Состоит из трёх частей:
- Инициализация:
int i = 0; - Условие:
i < collection.Length;— проверяется перед каждой итерацией. - Инкремент/декремент:
i++— выполняется после каждой итерации.
- Инициализация:
-
Позволяет гибко управлять индексом.
-
Может использоваться с любым типом коллекции, где есть индексы (
array,List<T>). -
forконтролирует индекс напрямую, поэтому можно перебирать частично, с шагом >1, в обратном порядке, и удалять/вставлять элементы по индексу.
foreach (var item in collection)
{
Console.WriteLine(item);
}- Итерация через enumerator (
GetEnumerator()) коллекции. - Не требует индекса.
- Более читаемый и безопасный для большинства коллекций.
- Не позволяет напрямую изменять коллекцию во время итерации (будет
InvalidOperationException). - Может быть более медленным для массивов при некоторых компиляторах, но часто оптимизируется JIT.
- При массиве: напрямую обращается к памяти через индекс → O(1) доступ.
- При
List<T>: обращение кlist[i]→ O(1) (динамический массив под капотом). - Нет создания дополнительных объектов (кроме счетчика цикла).
- Контроль над шагом (
i += 2,i--) — полностью в руках программиста. - Можно итерировать в обратном порядке, удалять элементы безопасно.
- Компилятор преобразует в
GetEnumerator()+MoveNext()+Current:
var enumerator = collection.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item);
}
}
finally
{
enumerator.Dispose();
}-
Если коллекция — структура (struct), например
List<T>.Enumerator:foreachиспользует struct, обычно без аллокаций.- Если коллекция приводится к интерфейсу
IEnumerable<T>— возможен боксинг структуры → аллокация в куче.
-
Если коллекция изменяется во время
foreach—InvalidOperationException(fail-fast).
| Характеристика | for |
foreach |
|---|---|---|
| Поддержка индекса | Да | Нет (только через Current) |
| Изменение коллекции во время итерации | Можно (при правильной логике) | Нельзя (InvalidOperationException) |
| Производительность | Быстро, особенно для массивов | Немного медленнее при IEnumerable, но оптимизировано для массивов и List |
| Читаемость кода | Менее читаемый при сложных условиях | Более читаемый, меньше ошибок |
| Возможность пропускать элементы/шаг | Да (i+=2) |
Нет (нужен LINQ или отдельная логика) |
| Контроль направления | Да (вперед, назад) | Нет, всегда по порядку |
| Использует enumerator | Нет | Да (GetEnumerator) |
| Аллокации | Минимальные | Возможны (для struct enumerator при boxing) |
forиforeachпочти идентичны по скорости.foreachкомпилятор оптимизирует в простой цикл с индексом.
for— прямой доступ по индексу.foreach— используетList<T>.Enumerator(struct) → обычно без аллокаций.- При использовании
IEnumerable<T>вместо конкретногоList<T>→ возможны аллокации.
forне применим напрямую (нет индекса).foreach— всегда используетLinkedList<T>.Enumerator.- Для
LinkedList<T>foreachоптимальнее, чем пытаться реализоватьforчерез узлы вручную.
-
Изменение коллекции
forпозволяет удалять элементы при обратном переборе.foreach— выброс исключения, если коллекция изменяется.
-
Boxing enumerator’ов
- Структура, используемая
foreach, может быть боксирована, если коллекция приводится кIEnumerable/IEnumerable<T>.
- Структура, используемая
-
Удаление элементов
// Правильно с for
for (int i = list.Count - 1; i >= 0; i--)
{
if (ShouldRemove(list[i])) list.RemoveAt(i);
}
// foreach выбросит InvalidOperationException- Пропуск шагов
for (int i = 0; i < 10; i+=2) { ... } // через один
// foreach не поддерживает такой шаг напрямую- Перформанс
- Для массивов и
List<T>:forиногда чуть быстрее (не создаётся enumerator). - Для сложных IEnumerable (например, LINQ-запросы):
foreachудобнее и безопаснее.
-
for- Нужно контролировать индекс.
- Модификация коллекции.
- Обход в обратном порядке.
- Частые манипуляции с шагом.
-
foreach- Только чтение элементов.
- Максимальная читаемость кода.
- Для коллекций без индекса (
LinkedList<T>,Dictionary<TKey,TValue>). - Минимизация ошибок (не забыть условие выхода, шаг, индексы).
-
Чем
forотличается отforeach?for— управляет индексом напрямую,foreach— через enumerator. -
Можно ли удалять элементы в
foreach? Нет, выбросInvalidOperationException. -
Почему
foreachиногда аллоцирует память? Если enumerator — struct, но коллекция приведена к интерфейсуIEnumerable. -
Что быстрее —
forилиforeachдля массивов? Обычно почти одинаково, иногдаforчуть быстрее (нет enumerator’а). -
Как перебрать
List<T>в обратном порядке безопасно?
for (int i = list.Count - 1; i >= 0; i--) { ... }✨Dvurechensky✨