Skip to content

Latest commit

 

History

History
236 lines (171 loc) · 11 KB

File metadata and controls

236 lines (171 loc) · 11 KB
layout default
title 🔁 Как работает цикл `for` и чем он отличается от `foreach`?
description
author Dvurechensky
date 2025-08-28
published true
tags
foreach
for
C#

🔁 Как работает цикл `for` и чем он отличается от `foreach`?

Typing SVG

Static Badge

✨ Оглавление

⬆ Вернуться к главной


1️⃣ Синтаксис и базовое поведение

Цикл for

for (int i = 0; i < collection.Length; i++)
{
    Console.WriteLine(collection[i]);
}
  • Состоит из трёх частей:

    1. Инициализация: int i = 0;
    2. Условие: i < collection.Length; — проверяется перед каждой итерацией.
    3. Инкремент/декремент: i++ — выполняется после каждой итерации.
  • Позволяет гибко управлять индексом.

  • Может использоваться с любым типом коллекции, где есть индексы (array, List<T>).

  • for контролирует индекс напрямую, поэтому можно перебирать частично, с шагом >1, в обратном порядке, и удалять/вставлять элементы по индексу.

Цикл foreach

foreach (var item in collection)
{
    Console.WriteLine(item);
}
  • Итерация через enumerator (GetEnumerator()) коллекции.
  • Не требует индекса.
  • Более читаемый и безопасный для большинства коллекций.
  • Не позволяет напрямую изменять коллекцию во время итерации (будет InvalidOperationException).
  • Может быть более медленным для массивов при некоторых компиляторах, но часто оптимизируется JIT.

2️⃣ Под капотом

for (массивы и List<T>)

  • При массиве: напрямую обращается к памяти через индекс → O(1) доступ.
  • При List<T>: обращение к list[i]O(1) (динамический массив под капотом).
  • Нет создания дополнительных объектов (кроме счетчика цикла).
  • Контроль над шагом (i += 2, i--) — полностью в руках программиста.
  • Можно итерировать в обратном порядке, удалять элементы безопасно.

foreach (IEnumerable/IEnumerator)

  • Компилятор преобразует в 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> — возможен боксинг структуры → аллокация в куче.
  • Если коллекция изменяется во время foreachInvalidOperationException (fail-fast).


3️⃣ Отличия for и foreach

Характеристика for foreach
Поддержка индекса Да Нет (только через Current)
Изменение коллекции во время итерации Можно (при правильной логике) Нельзя (InvalidOperationException)
Производительность Быстро, особенно для массивов Немного медленнее при IEnumerable, но оптимизировано для массивов и List
Читаемость кода Менее читаемый при сложных условиях Более читаемый, меньше ошибок
Возможность пропускать элементы/шаг Да (i+=2) Нет (нужен LINQ или отдельная логика)
Контроль направления Да (вперед, назад) Нет, всегда по порядку
Использует enumerator Нет Да (GetEnumerator)
Аллокации Минимальные Возможны (для struct enumerator при boxing)

4️⃣ Особенности при работе с разными коллекциями

Массивы (T[])

  • for и foreach почти идентичны по скорости.
  • foreach компилятор оптимизирует в простой цикл с индексом.

List<T>

  • for — прямой доступ по индексу.
  • foreach — использует List<T>.Enumerator (struct) → обычно без аллокаций.
  • При использовании IEnumerable<T> вместо конкретного List<T> → возможны аллокации.

LinkedList<T>

  • for не применим напрямую (нет индекса).
  • foreach — всегда использует LinkedList<T>.Enumerator.
  • Для LinkedList<T> foreach оптимальнее, чем пытаться реализовать for через узлы вручную.

5️⃣ Подводные камни

  1. Изменение коллекции

    • for позволяет удалять элементы при обратном переборе.
    • foreach — выброс исключения, если коллекция изменяется.
  2. Boxing enumerator’ов

    • Структура, используемая foreach, может быть боксирована, если коллекция приводится к IEnumerable/IEnumerable<T>.
  3. Удаление элементов

// Правильно с for
for (int i = list.Count - 1; i >= 0; i--)
{
    if (ShouldRemove(list[i])) list.RemoveAt(i);
}

// foreach выбросит InvalidOperationException
  1. Пропуск шагов
for (int i = 0; i < 10; i+=2) { ... } // через один
// foreach не поддерживает такой шаг напрямую
  1. Перформанс
  • Для массивов и List<T>: for иногда чуть быстрее (не создаётся enumerator).
  • Для сложных IEnumerable (например, LINQ-запросы): foreach удобнее и безопаснее.

6️⃣ Когда что использовать

  • for

    • Нужно контролировать индекс.
    • Модификация коллекции.
    • Обход в обратном порядке.
    • Частые манипуляции с шагом.
  • foreach

    • Только чтение элементов.
    • Максимальная читаемость кода.
    • Для коллекций без индекса (LinkedList<T>, Dictionary<TKey,TValue>).
    • Минимизация ошибок (не забыть условие выхода, шаг, индексы).

7️⃣ Вопросы на собесе

  1. Чем for отличается от foreach? for — управляет индексом напрямую, foreach — через enumerator.

  2. Можно ли удалять элементы в foreach? Нет, выброс InvalidOperationException.

  3. Почему foreach иногда аллоцирует память? Если enumerator — struct, но коллекция приведена к интерфейсу IEnumerable.

  4. Что быстрее — for или foreach для массивов? Обычно почти одинаково, иногда for чуть быстрее (нет enumerator’а).

  5. Как перебрать List<T> в обратном порядке безопасно?

for (int i = list.Count - 1; i >= 0; i--) { ... }

⬆ Вернуться к главной

✨Dvurechensky✨