快速回答:
foreach(
{
foreach(//do some stuff)
{
if(//check some condition)
{
goto end; // I'd probably add a comment here
}
}
// *1
}
end:
{} // the rest of your code.
但是,但是SESE...
SESE违规是指单一进入单一退出原则的违规。通过使用额外的条件很容易解决:
bool found = false;
for (int i=0; i<foo.Count && !found; ++i)
{
for (int j=0; j<bar.Count; ++j)
{
if (...) { found = true; }
}
// *1
if (!found) { ... }
}
那么为什么要在这里使用GOTO?
我认为创建适当、可维护的代码意味着使用最能描述你意图的语言结构。这里的“代码”始终由两个部分组成:
控制流,通过诸如for、while、break和goto之类的方式表达。
数据流,通过表达式、变量和其他内存访问来表达。
OP的意图是跳出嵌套循环,这相当于控制流操作。因此,我认为应该使用最能代表意图的控制流操作,即在这种情况下使用goto。
请注意,这绝不是滥用引入goto语句的原因;如果这样做,代码将变得非常难以阅读,这与可维护性和可读性无关。你应该将goto语句视为“最后一道控制流”语句,只有在精心制作的代码中极少使用。
也就是说,在这种情况下,除非绝对必要(例如没有可用于清晰表达意图的语言结构),否则不应创建本地变量来处理控制流。出于同样的原因,我不会在这种特定情况下使用Linq。
我想要性能。我该怎么做?
我认为大多数滥用语言结构的原因是不了解编译器如何处理代码,这就是为什么我习惯于解释它内部如何工作的一部分。请记住,我建议使用goto,因为它最清楚地描述了你的意图,而不是因为它可能会更快。以下是具体内容:
想象一下你是编译器。在代码的*1点处有大量代码,不能使用return。现在有两个选择:
你可以使用goto。
你可以使用额外的标志。
选项1将编译为一个字段,该字段具有内存。内存在某种程度上是“稀缺”的,因为编译器将尽力消耗尽可能少的内存,最好是在寄存器中。这就是你的性能来源。因此,编译器将尝试消除标志。
为此,编译器将进行大量的流分析和其他操作,以“确定”实际上有两条代码路径:一条是当标志设置时,另一条是当它没有设置时。
现在,如果你很幸运,编译器会有“aha”的时刻,并将你的代码从(2)改为简单的GOTO,在这种情况下,天空仍然是蓝色的,每个人都很开心。
然而,如果你运气不好(有很多实际原因会发生这种情况),它将无法通过流分析检测到并且不会创建GOTO。由于你的标志在内部循环中使用,甚至可能为此分配一个寄存器,这可能是最坏的情况。
如果一开始就使用了
goto
,就没有必要这样做。你只需给编译器正确的解决方案即可。简单明了。
你是否有关于编译器如何实现这一点的更多细节?
是的,请看Chandler的这个2小时视频,其中详细讲解了编译器的工作原理:
https://www.youtube.com/watch?v=FnGCDLhaxKU
-更新- 显然,有些人误解了我的故事,正如@Groo指出的那样。我做了一些调整以澄清我的意思。
goto
也是可以的,特别是当你无法使用return
语句时。 - Dmitry Bychenko