Menu

break, continue и goto в C#: выход из циклов и вложенных циклов

Как работают break, continue и goto в C#: досрочное завершение цикла, пропуск итерации, break внутри switch, три способа выйти из вложенных циклов (флаг, goto или метод с return) и бесконечный цикл из-за continue в while.

На этой странице есть исполняемые редакторы: меняйте, запускайте и сразу видите результат.

break досрочно завершает цикл. continue пропускает остаток текущей итерации и переходит к следующей. Оба относятся к самому внутреннему объемлющему циклу и одинаково работают в for, foreach, while и do while.

Вывод:

process order of 45
skip invalid total 0
process order of 120
skip invalid total -5
process order of 80
test order found, stopping
after the loop

continue перепрыгнул строку обработки для 0 и -5. break остановил цикл на 999, поэтому до 60 дело не дошло, и выполнение продолжилось с первого оператора после цикла.

break: остановиться, когда ответ известен

Типичное применение break это поиск: как только вы нашли то, что искали, нет смысла смотреть остальное.

Вывод:

first invalid email at index 1

После break итератор (i++) не выполняется, поэтому i сохранило бы индекс совпадения, если бы было объявлено вне цикла. Для простых поисков то же выражает библиотечный вызов: Array.FindIndex(emails, e => !e.Contains("@")) возвращает индекс (или -1), а emails.Any(e => !e.Contains("@")) из LINQ отвечает, есть ли плохой адрес.

continue: перейти к следующей итерации

continue это ранний выход для циклов. Вместо того чтобы оборачивать основную работу в if, отсеивайте ненужные случаи в начале:

Вывод:

pen costs 1.20
mug costs 8.00

Куда continue переходит дальше, зависит от цикла. В цикле for он выполняет итератор, а затем проверяет условие. В цикле while он сразу переходит к условию, что приводит к классической ошибке:

int i = 0;
while (i < 10)
{
    if (i % 2 == 0)
        continue;        // i++ below is skipped: i stays 0 forever
    Console.WriteLine(i);
    i++;
}

Обновляйте счётчик до любого continue или используйте цикл for, где обновление находится в заголовке и выполняется всегда.

break внутри switch внутри цикла

В switch оператор break завершает раздел case. Когда этот switch находится в цикле, break относится к switch, а не к циклу:

Вывод:

quit received
count = 2

Обычный break в case "quit" завершил бы только switch, цикл продолжился бы, и count стал бы равен 3. goto переходит к метке после цикла. Вынести цикл в метод и написать в этом case return тоже работает и не требует метки.

Выход из вложенных циклов

В C# нет break с меткой, как break outer; в Java. Когда поиск идёт по двум вложенным циклам, break во внутреннем цикле завершает только внутренний; внешний цикл переходит к следующей строке. Есть три стандартных решения.

1. Флаг, который проверяет внешний цикл:

Вывод:

first free seat: row 1, seat 2

2. goto к метке после обоих циклов. Это единственное место, где многие руководства по стилю C# допускают goto, потому что он точно выражает «выйти из обоих циклов»:

for (int row = 0; row < rows; row++)
{
    for (int col = 0; col < cols; col++)
    {
        if (seats[row, col] == 0)
        {
            Console.WriteLine($"first free seat: row {row}, seat {col}");
            goto done;
        }
    }
}
done:
Console.WriteLine("search finished");

3. Метод с return, обычно самый чистый вариант, потому что поиск получает имя и может вернуть результат:

Вывод:

first free seat: row 1, seat 2

C# 7.0 также ввёл локальные функции, которые позволяют поместить такой вспомогательный метод внутрь вызывающего метода. Использованный здесь приём с out-параметром описан на странице ref и out.

goto и метки

Метка это идентификатор с двоеточием, а goto label; переходит к ней. Правила достаточно строгие, чтобы не допустить худших злоупотреблений:

  • Метка должна находиться в том же методе, в том же блоке, что и goto, или в объемлющем. Выпрыгнуть из блоков можно, а запрыгнуть внутрь нельзя: переход снаружи к метке внутри тела цикла это ошибка CS0159 (no such label within the scope of the goto statement).
  • Выйти из блока finally через goto, break или continue нельзя.
  • Внутри switch goto case 3; и goto default; передают управление в другой раздел. Именно так switch в C# выражает проваливание, поскольку неявный переход в следующий case это ошибка компиляции.

Через goto можно строить и циклы (прыгая назад к метке), но цикл while выражает то же в форме, которую узнаёт любой читатель. Оставьте goto для выхода из вложенных циклов и для разделов switch.

return, throw и циклы

return выходит из всего метода, а значит, и из всех циклов в нём. throw делает то же, выбрасывая исключение. Внутри блока try с finally все операторы break, continue, goto и return выполняют блок finally, прежде чем управление пойдёт дальше, поэтому безопасно выходить из цикла, который держит файл открытым в блоке using.

ОператорИз чего выходитГде продолжает
continueтекущая итерацияследующая итерация (итератор, затем условие)
breakсамый внутренний цикл или switchоператор после него
goto labelлюбые объемлющие блокиметка
returnметодвызывающий код
throwвсё до подходящего catchблок catch

Часто задаваемые вопросы

Чем break отличается от continue в C#?

break сразу завершает самый внутренний цикл (или switch); выполнение продолжается после него. continue завершает только текущую итерацию и переходит к следующей: в цикле for затем выполняется итератор, в while и do while проверяется условие, в foreach берётся следующий элемент.

Как выйти из вложенных циклов в C#?

В C# нет break с меткой. Есть три варианта: установить флаг bool и проверять его во внешнем цикле, перепрыгнуть оба цикла через goto к метке или вынести циклы в метод и выйти из него через return. Вариант с методом обычно самый чистый, потому что заодно даёт поиску имя и возвращаемое значение.

Выходит ли break внутри switch из цикла в C#?

Нет. Внутри switch, находящегося в цикле, break завершает раздел switch, а цикл продолжается. Чтобы выйти из цикла изнутри case, используйте return, флаг, проверяемый после switch, или goto к метке после цикла. continue внутри case действительно переходит к следующей итерации цикла.

Разрешён ли goto в C#?

Да. goto label; переходит к label: в том же методе, но не может прыгнуть внутрь блока или выйти из finally. Внутри switch goto case value; и goto default; переходят между разделами. За пределами выхода из вложенных циклов и явного проваливания в switch goto редко бывает самым понятным выбором.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ