Программа курса← C#: Базовый курс
1Знакомство с C# и .NET
2Данные и типы
3Ветвления и циклы
4Методы и ошибки
5Коллекции и строки
6Классы и объекты

foreach, break и continue

Обойти коллекцию целиком – самая частая задача с циклами. Для неё есть отдельная конструкция, которая избавляет от счётчика, а вместе с ним и от ошибок с индексами.

foreach

string[] names = { "Аня", "Борис", "Вера" };

foreach (string name in names)
{
    Console.WriteLine(name);
}

Читается почти по-русски: «для каждого имени в списке имён». Индексов нет, границ нет, выйти за пределы массива невозможно в принципе.

Тот же обход через for выглядит так:

for (int i = 0; i < names.Length; i++)
{
    Console.WriteLine(names[i]);
}

Три места, где можно ошибиться (начальное значение, условие, индексация), против ни одного. Поэтому правило простое: нужны все элементы подряд – берите foreach.

Тип элемента обычно пишут как var – он и так известен из коллекции:

foreach (var name in names)

Чего foreach не умеет

Не даёт номер элемента. Если нужен индекс, придётся считать самому или брать for:

int index = 0;

foreach (var name in names)
{
    Console.WriteLine($"{index}. {name}");
    index++;
}

Не позволяет менять переменную цикла. Такая строка не соберётся:

foreach (var name in names)
{
    name = name.ToUpper();    // ошибка компиляции
}

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

Менять саму коллекцию во время foreach тоже нельзя – добавление или удаление элемента прерывает обход исключением InvalidOperationException. Если нужно удалить часть элементов, собирайте их в отдельный список, а удаляйте после цикла.

break: выйти из цикла

foreach (var name in names)
{
    if (name == "Борис")
    {
        Console.WriteLine("Нашли");
        break;
    }
}

break немедленно заканчивает цикл – оставшиеся элементы не проверяются. Это стандартный способ поиска: нашли – дальше искать незачем.

Из вложенных циклов break выходит только из своего, внутреннего. Чтобы прервать оба, обычно заводят флаг или выносят поиск в отдельный метод с return – о методах речь в следующем модуле.

continue: пропустить повторение

foreach (var number in numbers)
{
    if (number % 2 != 0)
    {
        continue;
    }

    Console.WriteLine(number);
}

continue завершает текущий проход и переходит к следующему элементу. Всё, что ниже, для этого элемента не выполняется.

Тот же результат даёт обычный if без continue, и для одного условия так даже короче. continue окупается, когда отсеивающих проверок несколько: без него тело уползает вправо на три уровня вложенности.

foreach (var order in orders)
{
    if (order is null) continue;
    if (order.IsCancelled) continue;
    if (order.Total <= 0) continue;

    Process(order);        // здесь остались только годные заказы
}

Такой приём называют ранним выходом. Читателю не нужно держать в голове три вложенных условия: он видит список причин пропустить элемент, а ниже – работу с тем, что прошло отбор.

Обход словаря

foreach работает с любой коллекцией, но у словаря элемент – это пара:

var ages = new Dictionary<string, int>
{
    ["Аня"] = 19,
    ["Борис"] = 25
};

foreach (var pair in ages)
{
    Console.WriteLine($"{pair.Key}: {pair.Value}");
}

Пару удобно сразу разложить на две переменные:

foreach (var (name, age) in ages)
{
    Console.WriteLine($"{name}: {age}");
}

Подробно про словари – в модуле про коллекции; здесь важно только то, что foreach обходит их так же, как массивы.

goto: почему его здесь нет

В C# есть оператор безусловного перехода goto, и он даже работает. Пользоваться им не нужно.

Любой цикл с goto записывается через for, while, break и continue – и в этом виде он читается, потому что границы повторяемого куска видны глазами. С goto границ нет: чтобы понять код, приходится прослеживать переходы вручную.

Единственное место, где goto в C# встречается на практике, – переход между ветками switch (goto case 2;). Но и там обычно проще перестроить условия.