#c# #.net #continue
#c# #.net #продолжайте
Вопрос:
Недавно я работал над проектом с открытым исходным кодом, и хотя я уже несколько лет занимаюсь разработкой в .NET, я раньше не натыкался на continue
ключевое слово.
Вопрос: Каковы некоторые лучшие практики или области, которые выиграли бы от использования continue
ключевого слова? Есть ли причина, по которой я, возможно, не видел этого раньше?
Комментарии:
1. Начиная с версии 3.5, люди используют предложения LINQ Where для обработки ситуаций, которые раньше решались с
continue
помощью .
Ответ №1:
Вы используете его для немедленного выхода из текущей итерации цикла и начала следующей, если это применимо.
foreach (var obj in list)
{
continue;
var temp = ...; // this code will never execute
}
continue
Обычно A привязывается к условию, и условие обычно может использоваться вместо continue
;
foreach (var obj in list)
{
if (condition)
continue;
// code
}
Может быть просто записано как
foreach (var obj in list)
{
if (!condition)
{
// code
}
}
continue
становится более привлекательным, если внутри цикла может быть несколько уровней вложенной if
логики. A continue
вместо вложенности может сделать код более читаемым. Конечно, рефакторинг цикла и условных выражений в соответствующие методы также сделает цикл более читаемым.
Комментарии:
1. как насчет цикломатической сложности, как предполагает мой компилятор? не лучше ли не вкладывать код, если это можно сделать?
Ответ №2:
continue
Ключевое слово используется, чтобы пропустить оставшуюся часть блока цикла и продолжить. Например:
for(int i = 0; i < 5; i )
{
if(i == 3) continue; //Skip the rest of the block and continue the loop
Console.WriteLine(i);
}
Будет печатать:
0
1
2
4
Ответ №3:
Это предотвращает глубокое гнездование.
foreach(var element in collection)
{
doSomething();
doSomethingElse();
if (condition1)
{
action1();
action2();
if (condition2)
{
action3();
}
}
}
может быть переписан как
foreach(var element in collection)
{
doSomething();
doSomethingElse();
if (!condition1)
{
continue;
}
action1();
action2();
if (!condition2)
{
continue;
}
action3();
}
Если блоки кода не являются тривиальными, а увеличены по вертикали, использование continue
может улучшить читаемость кода. Очевидно, что его следует использовать с осторожностью, как и любую другую языковую конструкцию.
Комментарии:
1. 1 за ответ на часть «Лучшая практика». Я действительно ненавижу эти if-блоки длиной в страницу.
Ответ №4:
Когда вы не хотите break
выходить из цикла, но хотите перейти к следующей итерации:
for (int i = 0; i < something; i )
{
if (condition)
continue;
// expensive calculations, skip due to continue
// or due to the condition above I don't want to
// run the following code
stuff();
code();
}
Ответ №5:
Вы должны использовать его экономно.
Лучшие (= самые простые для чтения) циклы не используют break
or continue
, они представляют собой своего рода структурированные операторы goto .
Сказав это, 1 или даже 2 оператора break / continue не сделают цикл нечитаемым, но стоит сделать их использование понятным и простым.
Комментарии:
1. Проблема в
goto
том, что он допускает произвольные переходы к любой точке кода.continue
перезапускает ближайший замкнутый цикл. Я не думаю, что критика в отношенииgoto
применима. Если они это делают, они также применяются кreturn
иthrow
.2. @recursive: эта (значительно сокращенная) критика, безусловно, относится к return (предпочтительно: метод должен иметь 1 точку выхода). Исключения — это совершенно другой вопрос.
Ответ №6:
В основном continue
и break
являются лучшими (но часто просто замаскированными) goto
операторами…
Всякий раз, когда вы находитесь внутри цикла и знаете, что все, что происходит дальше внутри цикла, должно быть пропущено и продолжено следующей итерацией, которую вы могли бы использовать continue
…
Как таковые, они должны использоваться редко… иногда они делают код очень читаемым и понятным (например, если альтернативой может быть несколько уровней вложенности)… большую часть времени они добавляют некоторую путаницу, подобную goto
.
Ответ №7:
Я предполагаю, почему вы не видели его ранее, continue
это своего рода двоюродный goto
брат, break
, и ранние return
s из функций. И мы все знаем, что Goto считается вредным, поэтому многие разработчики, вероятно, стремятся избегать его.
Что касается меня, я обычно использую continue
, когда хочу очистить цикл, где могут быть некоторые значения, которые меня не волнуют. Используя continue
I, я могу пропустить эти значения, не заключая «важную» логику в моем цикле во вложенный if
.
foreach (var v in GetSomeValues())
{
if (ThisValueIsNotImportant(v)) continue;
//Do important stuff with the value.
}
Ответ №8:
Я что-то упустил, не используя его?
Это кажется странным вопросом. Предположительно, вы знаете лучше, чем кто-либо другой, нужно ли вам начинать следующую итерацию цикла раньше.
Ответ №9:
Думайте об этом как о «возврате», но применимом только к контексту цикла. Типичным примером является конечный автомат, перебирающий все доступные входные данные.
while(!SomeQueue.Empty)
{
byte nextByte = SomeQueue.Dequeue();
switch State:
{
case A:
if(nextByte == Condition)
{
State = B;
}
else
{
State = ParseError;
}
continue;
case B:
//Test nextByte
State = C;
continue;
case C:
//Test nextByte
State = A;
continue;
case ParseError:
//Do something for this condition
State = A;
continue;
}
Ответ №10:
Несколько серьезных причин для отказа от реализации continue (или рефакторинга, чтобы удалить его, когда это возможно)
- Ваш цикл неэффективен — если вы используете
continue
, то используемый вами цикл обрабатывает элементы в списке, которые ему не нужно просматривать. Теперь с Jitted компиляторами или системами виртуальных машин, такими как Java, C # компилятор часто упрощает и реорганизует список при компиляции. Но вы все равно можете увеличить объем памяти, поскольку могут быть сохранены дополнительные объекты. Языки более низкого уровня будут иметь огромный успех. Реорганизуйте оператор цикла, чтобы перебирать только те элементы, которые необходимо затронуть. - Вы меняете, где лежит ответственность / логика цикла — цикл определяется его объявлением, именно здесь должно быть все управление циклом. Используя
continue
(и в меньшей степени ‘break’), вы создаете 2 разные строки, отвечающие за логику цикла, что еще хуже: продолжение может быть где угодно внутри кода внутри цикла (см. Следующий пункт). - Вы затрудняете внесение любых изменений в код и можете привести к ошибкам — это может показаться неочевидным для руководств и других простых примеров, но даже при использовании всего 10-20 строк кода в цикле это вызовет проблемы с производственным кодом. Допустим, у вас есть цикл по объектам, и вы смотрите на параметр объекта, чтобы определить, хотите ли вы продолжить в определенный момент. Теперь, когда этот объект будет изменен в будущем и этот параметр может быть изменен, вам нужно не только посмотреть на любую логику цикла, чтобы убедиться, что она обновлена, но вам уже нужно знать, что в цикле используется continue, а также найти его и изменить. Даже при наличии всего 10-20 строк кода внутри цикла это можно легко пропустить, что приведет к ситуации, когда человек, обновляющий код, знает, что он проверил логику цикла, и отправляется на поиски неизбежной ошибки в другом месте.
Некоторые примеры:
Continue используется без необходимости перебора ненужных элементов:
foreach(var item in list) {
if(item.param3 == "hippies") continue;
//edit the item here
}
Переработано (использует меньше памяти и процессора):
foreach(var item in list.Where(x => x.param3 != "hippies")) {
//edit item here
}
continue используется только для обновления некоторых частей элемента списка.
for(var item in itemList) { //must always be checked with any object change
item.param1 = 45;
item.param2 = "this is a thing";
if(item.param3 == "hippies") continue; //must be checked with any object code change
item.param3 = "dodgy code"
DoThisCode(item);
}
Теперь представьте приведенный выше пример, если item.param3 изменен в объектном коде, чтобы никогда не быть (или всегда) равным «hippies», представьте это с 20-50 строками кода в цикле (плохая практика, но это случается) с продолжением, сброшенным в середину, или даже хужепредставьте newParam
, что добавлен a, и теперь цикл должен что-то делать только тогда, когда newParam удовлетворяет определенному условию, а param3 не равен hippies . Человек, выполняющий обновление, должен знать, что continue используется, и обновлять его правильно, а также проверять условие цикла и убедиться, что оно хорошее, даже если в нем может быть указано обратное тому, что они ищут (param3 == «хиппи» вместо != «хиппи» или другой наборзначений)
Последний пример (основанный на реальном проекте, разработанном в 2018 году, который мне пришлось обновить, он значительно упрощен, реальный код цикла составлял 42 строки)
foreach(var item in list) {
if(item.param1 == condition)
{
someChangesMadeToItem(item);
}
else if(item.param3 == condition)
{
someMoreChangesMadeToItem(item);
}
//another 5-6 lines of code.
if(item.param2 == condition) {
continue;
}
someMoreChangesMadeToItem(item);
//another 20 lines of code here
result.add(item);
}
return resu<
В приведенном выше примере к объекту был добавлен другой параметр, который должен был запретить добавление к результатам (замена предыдущей логики).
Таким образом, цикл не только обрабатывал ненужные элементы, но и выполнял несколько строк кода для этих элементов, когда они никогда не выводились (поскольку они никогда не добавлялись в результат), и при обновлении я обновил логику цикла с помощью a .Where(x => x.NewParam condition)
и пропустил continue в середине кода, что привело кбольшая часть выходных данных верна, но некоторые элементы пропущены без видимой причины (где старое условие для продолжения оставалось верным), и только пройдя по коду и проверив список, я увидел условие, поскольку в цикле было несколько вызовов функций, и проблема могла возникнутьв любом из них.