在嵌套的while循环中继续执行

77
在这个代码示例中,是否有任何方法可以从catch块继续执行外部循环?
while
{
   // outer loop

   while
   {
       // inner loop
       try
       {
           throw;
       }
       catch 
       {
           // how do I continue on the outer loop from here?
           continue;
       }
   }
}

21
嵌套循环只会导致绝望。 - Michael Meadows
4
我觉得讽刺的是,当你向一群专家询问意见时,Stack Overflow 会关闭你的问题,但这些“专家”却无所顾虑地分享未经请求的常常无知的看法,而不是简单回答我们被告知必须提出的“寻求事实”的问题。 - greg
我同意,格雷格。迈克尔,在将来如果你要这样评论,请实际回答问题或提供一些链接。 - Slipoch
11个回答

124

更新:这个问题启发了我关于这个主题的文章。感谢这个好问题!


"continue" 和 "break" 只是 "goto" 的一种美化语法。通过给它们取可爱的名字并将它们的使用限制在特定的控制结构内,它们不再引起“所有 goto 都是坏的” 这样的争议。

如果你想做一个 continue 到外部循环,你可以在外部循环顶部定义一个标签(label),然后使用 "goto" 来跳转到那个标签。如果你觉得这样做不会妨碍代码的可读性,那么这可能是最方便的解决办法。

然而,我认为这是一个考虑重构你的控制流是否有益的机会。每当我在嵌套循环中使用条件 "break" 和 "continue" 时,我都会考虑进行重构。

请考虑:

successfulCandidate = null;
foreach(var candidate in candidates)
{
  foreach(var criterion in criteria)
  {
    if (!candidate.Meets(criterion))
    {  // TODO: no point in continuing checking criteria.
       // TODO: Somehow "continue" outer loop to check next candidate
    }
  }
  successfulCandidate = candidate;
  break;
}
if (successfulCandidate != null) // do something

两种重构技术:

第一种,将内部循环提取为一个方法:

foreach(var candidate in candidates)
{
  if (MeetsCriteria(candidate, criteria))
  { 
      successfulCandidate = candidate;
      break;
  }
}

其次,所有的循环都可以被消除吗?如果您正在循环搜索某些内容,请将其重构为查询。

var results = from candidate in candidates 
              where criteria.All(criterion=>candidate.Meets(criterion))
              select candidate;
var successfulCandidate = results.FirstOrDefault();
if (successfulCandidate != null)
{
  do something with the candidate
}

如果没有循环,则不需要使用break或continue!


11
对于“...将内部循环提取到一个方法中”,我会给予+1的评价。当我在代码审查中看到嵌套循环时,我通常需要很多的证明。它们通常会影响代码的可读性、可维护性和稳定性。OP的问题可以用简单的“return”或“throw”来解决(从而不依赖任何goto语句)。 - Michael Meadows
9
当你认为需要使用“goto”关键字时,首先停下来思考一下是否真的需要。如果你仍然需要使用它,那就直接使用它——这个关键字存在于语言中是有原因的。它本质上并不邪恶,只是通常出现在邪恶的模式中,因此应该作为一个信号,提示我们停下来尝试发现这些模式(而不是陷入“天哪,这个程序中用了‘goto’关键字,一切都错了”的恐慌中)。 - Pavel Minaev
2
Goto本身并不邪恶,但它是通往糟糕、懒惰代码的入口。在所有控制流程的方式中,它通常是最糟糕的。 - Michael Meadows
2
不要忘记为第二种重构技术添加 using System.Linq - Matthew Steven Monkan
2
这么啰嗦,像 Java 里的 continue {nameOfLoop} 呢.. :-( - Oliver Dixon
显示剩余3条评论

46
    while
    {
       // outer loop

       while
       {
           // inner loop
           try
           {
               throw;
           }
           catch 
           {
               // how do I continue on the outer loop from here?
               goto REPEAT;
           }
       }
       // end of outer loop
REPEAT: 
       // some statement or ; 
    }

问题已解决。(什么?为什么你们都用那种怪异的眼神看着我?)


4
我知道你在那里做了什么。 - Mastenka
7
如果你不明确添加一个空语句分号(REPEAT: ;), 代码可能无法编译。 - matpop

22
可以使用 break; 语句。
while
{
   while
   {
       try
       {
           throw;
       }
       catch 
       {
           break;
       }
   }
}

Continue用于跳回当前循环的顶部。

如果你需要跳出多个嵌套层级,你将不得不添加某种类型的“if”语句或使用可怕的/不推荐的“goto”。


4
这种方法的问题在于,如果在内循环结束和外循环结束之间需要进行额外的工作,当调用break时将执行该工作,但在调用continue时则不会执行。如果你不希望执行那段代码,就需要设置一个标志位。我并不是说这个答案是错的(实际上我点了赞),只是它看起来过于简单而有些误导人。 - Welbog

10

将 try/catch 结构与内部的 while 循环交换位置:

while {
  try {
    while {
      throw;
    }
  }
  catch {
    continue;
  }
}

5

编号。
我建议将内部循环提取为单独的方法。

while
{
   // outer loop
       try
       {
           myMethodWithWhileLoopThatThrowsException()
       }
       catch 
       {
           // how do I continue on the outer loop from here?
           continue;
       }
   }
}

这是有问题的,因为独立的方法将无法访问现有的局部变量。 - zvrba
7
这就是微软为什么要提供函数参数的原因。 - Welbog
将变量作为参数传递,或者如果需要副作用,则将其作为匿名委托发送到方法中执行。然后编译器将创建一个闭包,保留您的本地范围。 - Michael Meadows
5
你不应该使用异常处理流程来控制正常的代码流程。 - Brian Leeming
这就是微软为什么给我们带来了“本地函数”(自C#7和Visual Studio 2017)的原因,因为它们不需要任何参数,因为它们可以访问外部函数的所有局部变量。 - palota
@palota 你不能因为一个人不知道在他发表评论8年后才推出的功能而责怪他。 - AustinWBryan

3
在内部循环中使用break

1
如果在内部循环之后有一些代码,则无法正常工作。 - sinedsem

0
你只是想从内部中断,以便继续外部。
while
{
   // outer loop

   while
   {
       // inner loop
       try
       {
           throw;
       }
       catch 
       {
           // how do I continue on the outer loop from here?
           break;
       }
   }
}

请返回已翻译的文本。 - Jim G.

0

如果你想使用 try catch,只需将它们移到外部(尽管我建议重构)。

我只会在确实是错误并需要记录日志时这样做,我还会推荐

while
{
    try 
    {
        DoStuffToThisElement(item);
    }
    catch(Exception ex)
    {
        logError(ex);
    }
}

private void  DoStuffToThisElement(Item item)
{
    while
    {
        if(condition)
        {
            throw;
        }
    }
}

-1

我认为实现这个的最好方法是使用break语句。Break会结束当前循环从结束的位置继续执行。在这种情况下,它将结束内部循环跳回到外部while循环。你的代码应该像这样:

while
{
   // outer loop

   while
   {
       // inner loop
       try
       {
           throw;
       }
       catch 
       {
           // break jumps to outer loop, ends inner loop immediately.
           break; //THIS IS THE BREAK
       }
   }
}

我相信这就是你想要完成的,对吗?谢谢!


请返回已翻译的文本。 - Jim G.

-2
using System;

namespace Examples
{

    public class Continue : Exception { }
    public class Break : Exception { }

    public class NestedLoop
    {
        static public void ContinueOnParentLoopLevel()
        {
            while(true)
            try {
               // outer loop

               while(true)
               {
                   // inner loop

                   try
                   {
                       throw new Exception("Bali mu mamata");
                   }
                   catch (Exception)
                   {
                       // how do I continue on the outer loop from here?

                       throw new Continue();
                   }
               }
            } catch (Continue) {
                   continue;
            }
        } 
    }

}

}

你的大括号格式太混乱了,稍微保持一致性会更好。 - AustinWBryan

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