何时在C#中使用'continue'关键字

40

最近,我正在查看一个开源项目,虽然我已经使用.NET开发了几年,但我之前从未遇到过continue 关键字。

问题: 有哪些最佳实践或领域可以从使用continue关键字中受益?我之前为什么可能没有看到它?


7
自 3.5 版本以来,人们使用 LINQ Where 子句来处理以前需要用 continue 解决的情况。 - neontapir
10个回答

60

使用它可以立即退出当前循环迭代并开始下一个迭代,如果适用的话。

foreach (var obj in list)
{
    continue;

    var temp = ...; // this code will never execute
}

continue通常与一个条件相关联,而该条件通常可以代替continue

foreach (var obj in list)
{ 
    if (condition)
       continue;

    // code
} 

可以简单地写成

foreach (var obj in list)
{
    if (!condition)
    {
        // code
    }
}

continue如果你的循环体内有多层嵌套的if逻辑时,可能会变得更加吸引人。使用continue而不是嵌套可以使代码更易读。当然,重构循环和条件语句为适当的方法也会使循环更易读。


我的编译器建议使用圈复杂度,这样不嵌套代码会更好吧? - Alex Gordon

20

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

15

它可以防止深层嵌套。

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 可以提高代码的可读性。显然,与任何其他语言结构一样,应该慎重使用。


6
+1 表示对最佳实践部分的赞同。我真的很讨厌那些长达一页的 if 代码块。 - C.Evenhuis

12

当您不想从循环中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();
}

9

你应该节制使用它。

最好(即易于阅读)的循环不使用breakcontinue,它们是一种结构化的goto语句。

话虽如此,1或2个break/continue语句并不会使循环难以阅读,但明确使用它们并保持简单是值得的。


5
“goto”的问题在于它允许任意跳转到代码中的任何位置。“continue”重新启动最近的封闭循环。我认为反对“goto”的批评不适用。如果确实存在这样的问题,那么它们也适用于“return”和“throw”。 - recursive
1
@recursive:这(大大减少的)批评当然适用于return(更好的做法是一个方法应该只有一个出口点)。异常则完全是另一回事。 - H H

7

基本上,continuebreak是更好的(但经常只是伪装的)goto语句...

每当你在循环内部,并且知道接下来的所有内容都应该被跳过,并继续下一次迭代时,你可以使用continue...

因此,它们应该很少使用...有时它们使代码非常易读和清晰(例如,如果替代方案是几个层次的嵌套)...大多数时候它们会增加一些类似于goto的混乱。


6
我猜你之前没见过该语句的原因是continue有点类似于gotobreak以及函数中的早期return。我们都知道Goto被认为是有害的,因此许多开发人员可能会避免使用它。
对我而言,当我想要清理掉一些不关注的值时,我倾向于使用continue。通过使用continue,我可以跳过这些值,而无需将我的循环中“重要”的逻辑嵌套在一个嵌套的if中。
foreach (var v in GetSomeValues())
{
  if (ThisValueIsNotImportant(v)) continue;

  //Do important stuff with the value.
}

3
把它想象成一个“返回”,但仅适用于循环的上下文。一个常见的例子是状态机,在循环遍历所有可用的输入。
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;
}

3

如果我没有使用它,我会错过什么吗?

这似乎是一个奇怪的问题。你应该比任何其他人都更清楚是否需要提前开始下一次循环。


0

不推荐使用continue语句(或尽可能重构以删除它)的几个主要原因:

  1. 循环效率低下 - 如果你使用了continue,那么你所使用的循环会处理列表中不需要查看的元素。现在随着Jitted编译器或像Java、C#这样的VM系统,编译器通常会简化和重构列表。但是仍然可能会增加内存,因为额外的对象可能被存储。低级语言将受到巨大的打击。重构你的循环语句,只循环需要受影响的元素。
  2. 改变了循环的责任/逻辑所在 - 循环由其声明定义,这是所有循环控制的地方。通过使用continue (还有较小程度上的'break'),你正在使两行代码负责循环逻辑,更糟糕的是:continue语句可以出现在循环内部的任何位置(见下一点)
  3. 使任何代码更改变得更加困难,可能会引入错误 - 这在教程和其他简单示例中可能不明显,但即使在循环中只有10-20行代码,这也会导致生产代码出现问题。假设你循环遍历对象,并查看对象上的参数以确定是否要在某个点继续。现在当将来更改该对象并更改该参数时,你不仅需要查看任何循环逻辑以确保其已更新,而且你已经知道循环中使用了continue语句,并且需要找到它并进行修改。即使在循环内部只有10-20行代码,这也很容易被忽略,导致更新代码的人知道他们检查了循环逻辑并在其他地方寻找必然的错误。

一些例子:

不必要地使用continue语句循环未使用的项目:

foreach(var item in list) {
    if(item.param3 == "hippies") continue;
    //edit the item here
}

重构后(使用更少的内存和CPU):

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行代码(这是不好的实践,但它确实会发生),并且在中间加入了一个continue,甚至更糟的是,假设添加了一个newParam,循环现在应该只在newParam满足某个条件且param3不等于hippies时执行某些操作。更新的人必须知道continue正在使用并正确更新它,同时检查循环条件并确保它是正确的,即使它可能陈述了与他们所寻找的相反的内容(param3 ==“hippies”而不是!=“hippies”或另一组值)。
最后一个例子(基于我在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 result;

在上面的示例中,对象添加了另一个参数,该参数应禁止添加到结果中(替换先前的逻辑)。
因此,不仅循环处理不必要的项目,而且在这些项目从未输出时执行了多行代码(因为它们从未添加到结果中),当我使用.Where(x => x.NewParam条件)更新了循环逻辑并错过了代码中间的continue时,大部分输出都是正确的,但由于没有立即显而易见的原因错过了一些项目(其中旧的continue条件成立),只有通过逐步执行代码并检查列表才能看到条件,因为循环中有多个函数调用,问题可能发生在任何一个函数中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接