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нельзя. - Внутри
switchgoto 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 редко бывает самым понятным выбором.