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
遇到 0 和 -5 时,continue 跳过了处理那一行。遇到 999 时,break 停止了循环,所以 60 根本没有被访问到,程序从循环后的第一条语句继续执行。
break:知道答案就停下
break 的典型用法是查找:一旦找到了要找的东西,就没有必要再看剩下的部分。
输出:
first invalid email at index 1
执行 break 之后,迭代语句(i++)不会再运行,所以如果 i 声明在循环外面,它仍然保存着匹配项的索引。对于简单的查找,也可以用库函数表达同样的意思:Array.FindIndex(emails, e => !e.Contains("@")) 返回索引(找不到时返回 -1),而 LINQ 的 emails.Any(e => !e.Contains("@")) 告诉你是否存在无效的邮箱。
continue:跳到下一次迭代
continue 就是循环里的卫语句。不必把真正的工作包在一个 if 里,而是在开头就把不需要的情况排除掉:
输出:
pen costs 1.20
mug costs 8.00
continue 之后去哪里,取决于循环的类型。在 for 循环中,它先执行迭代语句,再检查条件。在 while 循环中,它直接跳到条件判断,这会导致一个经典 bug:
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 循环,它的更新语句写在循环头里,总会执行。
循环中 switch 里的 break
在 switch 中,break 结束的是 case 分支。当这个 switch 位于循环中时,break 属于 switch,而不属于循环:
输出:
quit received
count = 2
如果在 "quit" 分支里只写 break,它只会结束 switch,循环会继续,count 将是 3。goto 则跳到循环之后的标签。把循环移到一个方法中,并在这个分支里写 return,同样可行,而且不需要标签。
跳出嵌套循环
C# 没有 Java 中 break outer; 那样带标签的 break。当查找要遍历两层嵌套循环时,内层循环中的 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(在 goto 语句的作用域内没有这样的标签)。 - 不能用
goto、break或continue离开finally块。 - 在
switch中,goto case 3;和goto default;会把控制权转到另一个分支。C# 的 switch 就是这样表达贯穿(fall-through)的,因为隐式地落入下一个 case 是编译错误。
goto 也能构造循环(向后跳到某个标签),但 while 循环能以每个读者都熟悉的形式表达同样的意思。goto 只留给跳出嵌套循环和 switch 分支跳转。
return、throw 与循环
return 离开整个方法,因此也会离开方法中的所有循环。throw 通过抛出异常达到同样的效果。在带有 finally 的 try 块中,break、continue、goto 和 return 都会先执行 finally 块,再转移控制权,所以从一个在 using 块中打开了文件的循环里 break 出来是安全的。
| 语句 | 离开 | 从哪里继续 |
|---|---|---|
continue | 当前迭代 | 下一次迭代(先迭代语句,再条件) |
break | 最内层的循环或 switch | 其后的语句 |
goto label | 任意外层代码块 | 标签处 |
return | 方法 | 调用方 |
throw | 直到匹配的 catch 为止的所有层级 | catch 块 |
常见问题
C# 中 break 和 continue 有什么区别?
break 立即结束最内层的循环(或 switch),从它后面的语句继续执行。continue 只结束当前这次迭代,跳到下一次:在 for 循环中接着执行迭代语句,在 while 和 do while 中接着检查条件,在 foreach 中接着取下一个元素。
C# 中如何跳出嵌套循环?
C# 没有带标签的 break。有三种做法:设置一个 bool 标志变量并在外层循环中检查它;用 goto 跳到两层循环之后的标签;或者把循环移到一个方法里,然后 return。方法的写法通常最清晰,因为它还给这次查找起了个名字,并能返回结果。
C# 中 switch 里的 break 会跳出循环吗?
不会。在循环中的 switch 里,break 只结束 switch 的这个分支,循环会继续。要在某个 case 里离开循环,可以用 return、在 switch 之后检查的标志变量,或者 goto 到循环之后的标签。而 case 里的 continue 确实会进入循环的下一次迭代。
C# 中可以用 goto 吗?
可以。goto label; 会跳到同一个方法中的 label:,但不能跳进一个代码块,也不能跳出 finally。在 switch 中,goto case value; 和 goto default; 可以在分支之间跳转。除了跳出嵌套循环和显式的 switch 贯穿,goto 很少是最清晰的选择。