如何在C#中从第二个嵌套的foreach循环中跳出第一个foreach循环

8

如何在C#中从第二个嵌套的for each循环中打破第一个for each循环,我想在第二个for each循环中检查一些条件,然后尝试打破父级for each循环。

foreach(//do some stuff)
{
     foreach(//do some stuff)
     {
          if(//check some condition)
          {
                break;//but want to break first foreach loop
          }
     }
}
6个回答

9

快速回答:

foreach(//do some stuff)
{
     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指出的那样。我做了一些调整以澄清我的意思。

5
好的,下面是内容翻译:标题:研究面对屏幕,男子手握着杯子,旁边是电脑和一叠纸张。他说:“嗯,我又发现了一些很有趣的事情,但是还是搞不清怎么用。”女子在他背后站立,看着屏幕。她说:“呃,那就拿去给别人看啊。”男子回答:“你开玩笑吧?这会让别人知道我不知道怎么用这个东西。”女子耸了耸肩说:“好吧,那你应该怎么办呢?”男子回答:“我想我会再花费几个小时来试图自己理解它。”女子无奈地说:“哦,好吧,祝你好运。” - Loofer
1
@Loofer:偶尔使用goto也是可以的,特别是当你无法使用return语句时。 - Dmitry Bychenko
@Loofer 循环结构也会被编译成 IL GOTO 和 LABEL。但如果有其他替代方案,最好不要养成过度使用循环的习惯。 - atlaste
2
引用一位专家的话:“程序员浪费了大量时间思考或担心程序中非关键部分的速度,而这些效率的尝试实际上在调试和维护时有着强烈的负面影响。我们应该忘记小的效率问题,大约97%的时间都是如此:过早地优化是万恶之源。然而,在关键的3%机会上,我们也不应该放弃。” - vgru
最后,第四点)这不是关于过早优化的问题 - 嗯,你的回答几乎是本帖中最长的,几乎完全集中在编译器、流分析、CPU寄存器上,得出结论应该“简单地给编译器正确的解决方案”。你甚至还添加了一个关于编译器的2小时视频链接。 - vgru
显示剩余4条评论

5
您可以通过使用“标记”来实现此目的。
bool breakout = false
foreach(//do some stuff)
{
     foreach(//do some stuff)
     {
          if(//check some condition)
          {
             breakout = true;
             break;
          }
     }
     if(breakout)
      break;
}

3
如果无法使用return,我建议使用Linq,这可以使您的代码更易读:
Boolean found = false;

foreach(var item1 in source1.TakeWhile(_ => !found)) {
     foreach(var item2 in source2.TakeWhile(_ => !found)) {
          if (some condition)
          {
               found = true;

               //break; // Not necessary
          }
     }
}

2
“可读性”可能因人而异,我猜。 :) - vgru

2
您可以尝试像这样使用return
foreach(//do some stuff)
    foreach(//do some stuff)
       if(//check some condition)
          return;

7
虽然这可能满足原帖的目的,return不仅会跳出循环,还会跳出包含循环的函数!而且公平地对待其他建议答案的人,原帖没有提到这些循环是函数的一部分,该函数返回他们正在循环获取的值。 - rskar

1
bool doBreak = false;
foreach(//do some stuff)
{
    foreach(//do some stuff)
    {
        doBreak = <check some condition>;
        if(doBreak) break;
    }
    if(doBreak) break;
}

1
您可以使用WHILE。也许这段代码对您有帮助。
foreach(// Some condition here)
{
    //solution
    bool breakme = false;

    while (// Some condition here)
    {
        foreach (// Some condition here)
        {
            if (// Condition again)
            {
                //Do some code
            }
            if (// Condition again)
            {
                //Stop the first foreach then go back to first foreach
                breakme = true;
                break;
            }
        }
    }
    if(breakme)
    {
        break;
    }
}

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