【问题标题】:Breaking out of a nested loop跳出嵌套循环
【发布时间】:2010-09-24 09:37:26
【问题描述】:

如果我有一个嵌套在另一个循环中的 for 循环,我怎样才能以最快的方式有效地退出两个循环(内部和外部)?

我不想必须使用布尔值然后不得不说转到另一个方法,而只是在外循环之后执行第一行代码。

有什么快速又好的方法来解决这个问题?

我认为异常并不便宜/应该只在真正异常的情况下抛出等。因此,从性能角度来看,我认为这种解决方案不会很好。

我认为利用 .NET 中的新功能(匿名方法)来做一些非常基础的事情是不对的。

【问题讨论】:

  • 我只是想确定:你为什么要这样做?
  • 为什么不想使用布尔值?这样做有什么问题?
  • 在 VB.net 中,您可以将 try/finally 语句(没有 catch)包裹在任意数量的循环周围,然后“exit try”将在任何时候退出所有循环。

标签: c# for-loop nested-loops


【解决方案1】:

我认为,除非您想做“布尔值”,否则唯一的解决方案实际上就是抛出。你显然不应该这样做..!

【讨论】:

    【解决方案2】:

    好吧,goto,但这很丑陋,而且并不总是可能的。您还可以将循环放入方法(或匿名方法)中,然后使用return 退出回到主代码。

        // goto
        for (int i = 0; i < 100; i++)
        {
            for (int j = 0; j < 100; j++)
            {
                goto Foo; // yeuck!
            }
        }
    Foo:
        Console.WriteLine("Hi");
    

    对比:

    // anon-method
    Action work = delegate
    {
        for (int x = 0; x < 100; x++)
        {
            for (int y = 0; y < 100; y++)
            {
                return; // exits anon-method
            }
        }
    };
    work(); // execute anon-method
    Console.WriteLine("Hi");
    

    请注意,在 C# 7 中,我们应该获得“本地函数”,这(语法待定等)意味着它应该像这样工作:

    // local function (declared **inside** another method)
    void Work()
    {
        for (int x = 0; x < 100; x++)
        {
            for (int y = 0; y < 100; y++)
            {
                return; // exits local function
            }
        }
    };
    Work(); // execute local function
    Console.WriteLine("Hi");
    

    【讨论】:

    • 在这种情况下,我不认为使用 goto 比正常使用 break 之类的东西更糟糕(毕竟它们都是标签的无条件分支,只是使用 break标签是隐含的)。
    • 有时 goto 不如其他选项那么邪恶
    • Goto 本身并不难看。丑陋的是滥用 goto 导致意大利面条代码。使用 goto 跳出嵌套循环是完全可以的。此外,请注意,从结构化编程的角度来看,所有的 break、continue 和 return 都几乎不比 goto 好——基本上它们是一样的,只是包装更好。这就是为什么纯结构语言(例如原始的 Pascal)缺少这三种语言。
    • @el.pescado 是完全正确的:许多编码人员被误导认为goto 是有害的per-se,而它只是正确 b> 仪器做一些事情,和很多仪器一样可能被误用。坦率地说,这种对goto 的宗教厌恶非常愚蠢,而且绝对不科学。
    • C# 有一个 goto 似乎很奇怪,但它不像其他语言那样有标签 breakcontinue。鉴于这种语言限制,我认为goto 正是解决此问题的正确方法。
    【解决方案3】:

    是否可以将嵌套的 for 循环重构为私有方法?这样你就可以简单地从方法中“返回”退出循环。

    【讨论】:

    • 还有一个好处是让你的原始方法更短:-)
    • C++11 lambda 在某些情况下使这变得容易:[&amp;] { ... return; ... }();
    【解决方案4】:

    请不要引用我的话,但您可以按照 MSDN 中的建议使用 goto。还有其他解决方案,包括在两个循环的每次迭代中检查的标志。最后,您可以使用异常作为解决问题的真正重量级解决方案。

    转到:

    for ( int i = 0; i < 10; ++i ) {
       for ( int j = 0; j < 10; ++j ) {
          // code
          if ( break_condition ) goto End;
          // more code
       }
    }
    End: ;
    

    条件:

    bool exit = false;
    for ( int i = 0; i < 10 && !exit; ++i ) {
       for ( int j = 0; j < 10 && !exit; ++j ) {
          // code
          if ( break_condition ) {
             exit = true;
             break; // or continue
          }
          // more code
       }
    }
    

    例外:

    try {
        for ( int i = 0; i < 10 && !exit; ++i ) {
           for ( int j = 0; j < 10 && !exit; ++j ) {
              // code
              if ( break_condition ) {
                 throw new Exception()
              }
              // more code
           }
        }
    catch ( Exception e ) {}
    

    【讨论】:

    • 这些都是 hacky 的变通方法,如果只考虑一个方法并使用提前返回就非常干净
    • :) 对,这是一个简单的解决方案,但是您必须将所有必需的本地数据作为参数传递给方法...这是 goto 可能是合适解决方案的少数几个地方之一
    • Condition 方法甚至不起作用,因为“更多代码”将在退出内循环后执行一次,然后再退出外循环。 GOTO 方法有效,但正是海报所说的他们不想做的事情。 Exception 方法有效,但比 GOTO 更丑更慢。
    • 我会在标签后突出显示分号。这样,该标签甚至可以位于块的末尾。 +1
    • @Windowsprogrammer OP 问道:“有什么快速又好的方法来解决这个问题?” Goto 是首选的解决方案:简洁明了,不涉及单独的方法。
    【解决方案5】:

    在外循环中使用合适的防护。在你中断之前在内部循环中设置守卫。

    bool exitedInner = false;
    
    for (int i = 0; i < N && !exitedInner; ++i) {
    
        .... some outer loop stuff
    
        for (int j = 0; j < M; ++j) {
    
            if (sometest) {
                exitedInner = true;
                break;
            }
        }
        if (!exitedInner) {
           ... more outer loop stuff
        }
    }
    

    或者更好的是,将内循环抽象为一个方法,当它返回 false 时退出外循环。

    for (int i = 0; i < N; ++i) {
    
        .... some outer loop stuff
    
        if (!doInner(i, N, M)) {
           break;
        }
    
        ... more outer loop stuff
    }
    

    【讨论】:

    • 除了 OP 说“我不想使用布尔值”。
    • 非常帕斯卡风格...我可能更愿意使用 goto,尽管我通常会像瘟疫一样避免使用它们。
    【解决方案6】:

    考虑到一个函数/方法并使用提前返回,或者将你的循环重新排列成一个while子句。 goto/exceptions/whatever 在这里肯定不合适。

    def do_until_equal():
      foreach a:
        foreach b:
          if a==b: return
    

    【讨论】:

      【解决方案7】:

      您要求将快速、美观、不使用布尔值、不使用 goto 和 C# 结合起来。你已经排除了所有可能的方式来做你想做的事。

      最快最不难看的方法是使用 goto。

      【讨论】:

      • 完全同意。引入一种新方法只是为了摆脱单个 goto 是愚蠢的。如果编译器由于某种原因无法内联该方法调用 - 我们最终将获得完全不必要的额外方法调用开销。抛出和捕获异常只是为了打破循环既冗长又可笑地昂贵。
      • @Windowsprogramm:OP 没有要求“不使用 goto”。他不想“采用另一种方法”。这个问题远没有排除所有可能的方法,但你暗示 goto 在这里最好是正确的。
      【解决方案8】:

      C# 适应 C 中常用的方法 - 在循环条件之外设置外循环变量的值(即,使用 int 变量 INT_MAX -1 的 for 循环通常是不错的选择):

      for (int i = 0; i < 100; i++)
      {
          for (int j = 0; j < 100; j++)
          {
              if (exit_condition)
              {
                  // cause the outer loop to break:
                  // use i = INT_MAX - 1; otherwise i++ == INT_MIN < 100 and loop will continue 
                  i = int.MaxValue - 1;
                  Console.WriteLine("Hi");
                  // break the inner loop
                  break;
              }
          }
          // if you have code in outer loop it will execute after break from inner loop    
      }
      

      正如代码中的注释所说,break 不会神奇地跳转到外循环的下一次迭代 - 因此,如果您有在内循环之外的代码,这种方法需要更多检查。在这种情况下考虑其他解决方案。

      这种方法适用于forwhile 循环,但不适用于foreach。在foreach 的情况下,您将无法访问隐藏的枚举器的代码,因此您无法更改它(即使您可以,IEnumerator 也没有一些“MoveToEnd”方法)。

      致谢内联 cmets 的作者:
      i = INT_MAX - 1Meta 的建议
      for/foreachygoe 发表评论。
      正确的IntMax by jmbpiano
      blizpasta关于内循环后代码的备注

      【讨论】:

      • @DrG:在 c# 中不起作用,因为“break 语句终止了它出现的最近的封闭循环或 switch 语句。” (msdn)
      • @blizpasta 所以?如果他使外循环上的条件为假(就像他所做的那样),它将同时退出。
      • @Patrick 我错了。谢谢。它适用于内部循环之后没有语句的嵌套循环。
      • 你应该使用 i = INT_MAX - 1;否则 i++ == INT_MIN
      • @ktutnik 它不适用于 foreach,因为您没有代码访问隐藏的枚举器。 IEnumerator 也没有一些“MoveToEnd”方法。
      【解决方案9】:

      自从几十年前我第一次在 C 中看到 break 以来,这个问题一直困扰着我。我希望某些语言增强功能可以扩展 break 从而起作用:

      break; // our trusty friend, breaks out of current looping construct.
      break 2; // breaks out of the current and it's parent looping construct.
      break 3; // breaks out of 3 looping constructs.
      break all; // totally decimates any looping constructs in force.
      

      【讨论】:

      • 然后维护程序员将插入另一层嵌套,将修复一些中断语句,并将中断一些其他中断语句。解决方法是改为使用标签。确实有人提出过,但实用主义者使用 goto 标签代替。
      • 等等,谁再做维护编程了? :)
      • JavaScript 甚至有标记的块/中断语句。 devguru.com/Technologies/ecmascript/quickref/break.html
      • @Chris Bartow:酷!让我的圣诞节 :) @David Grant: 所以看起来 JS break == C's goto?
      • D 标记了中断/继续
      【解决方案10】:

      我见过很多使用“break”但没有使用“continue”的示例。

      它仍然需要内部循环中的某种标志:

      while( some_condition )
      {
          // outer loop stuff
          ...
      
          bool get_out = false;
          for(...)
          {
              // inner loop stuff
              ...
      
              get_out = true;
              break;
          }
      
          if( get_out )
          {
              some_condition=false;
              continue;
          }
      
          // more out loop stuff
          ...
      
      }
      

      【讨论】:

        【解决方案11】:

        此解决方案不适用于 C#

        对于通过其他语言发现此问题的人,Javascript、Java 和 D 允许标记中断并继续

        outer: while(fn1())
        {
           while(fn2())
           {
             if(fn3()) continue outer;
             if(fn4()) break outer;
           }
        }
        

        【讨论】:

        • 很遗憾,这不能用 c# 完成,它会在很多时候产生更清晰的代码。
        • 我真的兴奋了一秒钟,直到我意识到这不是 c# 的。 :(
        • 这个概念也适用于 PowerShell,以防有人遇到问题。 (他们只是将冒号放在标签名称前面。)现在我知道这不是特定于 PowerShell 的……这种语法与 C# 中可用的 goto 标签不兼容。 PHP 使用了别的东西:break 3;将级别数放在 break 语句之后。
        • 这是 java 不是 c#
        • 想要在 C# 中看到这个功能的人,请在这里点赞,? github.com/dotnet/csharplang/issues/869#issuecomment-326606003
        【解决方案12】:
                 bool breakInnerLoop=false
                for(int i=0;i<=10;i++)
                {
                  for(int J=0;i<=10;i++)
                  {
                      if(i<=j)
                        {
                            breakInnerLoop=true;
                            break;
                        }
                  }
                    if(breakInnerLoop)
                    {
                    continue
                    }
                }
        

        【讨论】:

        • 和dviljoen的回答有什么本质区别?
        • 那不行,因为你没有检查外部for循环中的“breakInnerLoop”条件,所以你只是迭代到下一个循环,你也写了j和J,错过了一个分号,不会编译的。
        【解决方案13】:

        我记得在我的学生时代,有人说从数学上证明你可以在没有 goto 的情况下在代码中做任何事情(即没有任何情况下 goto 是唯一的答案)。所以,我从不使用 goto(只是我个人的喜好,不代表我是对还是错)

        无论如何,为了打破嵌套循环,我会这样做:

        var isDone = false;
        for (var x in collectionX) {
            for (var y in collectionY) {
                for (var z in collectionZ) {
                    if (conditionMet) {
                        // some code
                        isDone = true;
                    }
                    if (isDone)
                        break;
                }
                if (isDone) 
                    break;
            }
            if (isDone)
                break;
        }
        

        ...我希望对那些喜欢我的人有所帮助是反goto“fanboys”:)

        【讨论】:

        • 很抱歉告诉你,但你的讲师应该为你的病情负责。如果他费心强迫你学习汇编,那么你知道'goto'只是一个跳跃(当然我忽略了这是一个c#问题的事实)。
        • 我得说,鉴于几乎所有代码都是以您指定首先做什么的方式编写的(例如 if ( ) {} 而不是 {} if ( ) 并在order) 因为更容易看到正在发生的事情,Goto 似乎基本上与所代表的一切相矛盾。老实说,我希望他们能添加标签 for's 和 while's,因为它出现在“break”之前,这样你就知道你的突破是什么,而不是在未来到达开发人员还不知道的某个神奇点
        【解决方案14】:

        有时将代码抽象为它自己的函数而不是使用提前返回——虽然提前返回是邪恶的:)

        public void GetIndexOf(Transform transform, out int outX, out int outY)
        {
            outX = -1;
            outY = -1;
        
            for (int x = 0; x < Columns.Length; x++)
            {
                var column = Columns[x];
        
                for (int y = 0; y < column.Transforms.Length; y++)
                {
                    if(column.Transforms[y] == transform)
                    {
                        outX = x;
                        outY = y;
        
                        return;
                    }
                }
            }
        }
        

        【讨论】:

        • 但这允许 OP 要求的传统解决方案
        【解决方案15】:

        您甚至看过break 关键字吗?哎哟

        这只是伪代码,但你应该能明白我的意思:

        <?php
        for(...) {
            while(...) {
                foreach(...) {
                    break 3;
                }
            }
        }
        

        如果你认为break 是一个类似break() 的函数,那么它的参数就是要跳出的循环数。由于我们在此处的代码中处于第三个循环,因此我们可以打破所有三个循环。

        手册:http://php.net/break

        【讨论】:

          【解决方案16】:

          抛出一个自定义异常,该异常退出外部循环。

          它适用于 forforeachwhile 或任何类型的循环和任何使用 try catch exception 块的语言

          try 
          {
             foreach (object o in list)
             {
                foreach (object another in otherList)
                {
                   // ... some stuff here
                   if (condition)
                   {
                      throw new CustomExcpetion();
                   }
                }
             }
          }
          catch (CustomException)
          {
             // log 
          }
          

          【讨论】:

            【解决方案17】:

            我看到你接受了这个人提到你的 goto 语句的答案,在现代编程和专家意见中 goto 是一个杀手,我们称它为编程中的杀手,有一些特定的原因,我不会讨论此时就到这里了,但是您的问题的解决方案非常简单,您可以在这种情况下使用布尔标志,就像我将在我的示例中演示的那样:

                        for (; j < 10; j++)
                        {
                            //solution
                            bool breakme = false;
                            for (int k = 1; k < 10; k++)
                            {
                               //place the condition where you want to stop it
                                if ()
                                {
                                    breakme = true;
                                    break;
                                }
                            }
            
                            if(breakme)
                                break;
                           }
            

            简单明了。 :)

            【讨论】:

            • 在建议休息之前阅读问题。因此投反对票。
            • 现在阅读,但是当我发布此答案时,未添加未使用布尔值的编辑版本这就是我发布此答案的原因..但感谢您的反对!
            • 这不是个人的事情。不幸的是,投票现在已锁定;)这是 SO 的必要部分,以获得最佳答案(这并不总是发生)
            【解决方案18】:

            在我看来,人们似乎非常不喜欢 goto 声明,所以我觉得有必要稍微澄清一下。

            我相信人们对goto 的“情绪”最终归结为对代码的理解以及对可能的性能影响的(误解)。因此,在回答这个问题之前,我将首先详细介绍它是如何编译的。

            众所周知,C# 编译为 IL,然后使用 SSA 编译器编译为汇编器。我将对这一切如何运作提供一些见解,然后尝试回答问题本身。

            从 C# 到 IL

            首先我们需要一段 C# 代码。让我们从简单的开始:

            foreach (var item in array)
            {
                // ... 
                break;
                // ...
            }
            

            我将逐步执行此操作,让您对幕后发生的事情有一个很好的了解。

            第一次翻译:从foreach 到等效的for 循环(注意:我在这里使用数组,因为我不想深入了解 IDisposable 的细节——在这种情况下,我也会有使用 IEnumerable):

            for (int i=0; i<array.Length; ++i)
            {
                var item = array[i];
                // ...
                break;
                // ...
            }
            

            第二次翻译:forbreak 被翻译成更简单的等价物:

            int i=0;
            while (i < array.Length)
            {
                var item = array[i];
                // ...
                break;
                // ...
                ++i;
            }
            

            第三次翻译(这相当于IL代码):我们把breakwhile改成一个分支:

                int i=0; // for initialization
            
            startLoop:
                if (i >= array.Length) // for condition
                {
                    goto exitLoop;
                }
                var item = array[i];
                // ...
                goto exitLoop; // break
                // ...
                ++i;           // for post-expression
                goto startLoop; 
            

            虽然编译器在一个步骤中完成了这些事情,但它可以让您深入了解该过程。从 C# 程序演变而来的 IL 代码是最后一个 C# 代码的直译。您可以在这里亲自查看:https://dotnetfiddle.net/QaiLRz(点击“查看 IL”)

            现在,您在这里观察到的一件事是,在此过程中,代码变得更加复杂。观察这一点的最简单方法是我们需要越来越多的代码来完成同样的事情。你可能还会争辩说foreachforwhilebreak 实际上是goto 的简写,这在一定程度上是正确的。

            从 IL 到汇编程序

            .NET JIT 编译器是一个 SSA 编译器。我不会在这里详细介绍 SSA 表单的所有细节以及如何创建优化编译器,它太多了,但可以对将会发生的事情给出一个基本的了解。为了更深入地了解,最好开始阅读优化编译器(我喜欢这本书的简要介绍:http://ssabook.gforge.inria.fr/latest/book.pdf)和 LLVM (llvm.org)。

            每个优化编译器都依赖于代码简单并遵循可预测模式这一事实。在 FOR 循环的情况下,我们使用图论来分析分支,然后在我们的分支中优化诸如循环之类的东西(例如向后分支)。

            但是,我们现在有了前向分支来实现我们的循环。您可能已经猜到了,这实际上是 JIT 修复的第一步,如下所示:

                int i=0; // for initialization
            
                if (i >= array.Length) // for condition
                {
                    goto endOfLoop;
                }
            
            startLoop:
                var item = array[i];
                // ...
                goto endOfLoop; // break
                // ...
                ++i;           // for post-expression
            
                if (i >= array.Length) // for condition
                {
                    goto startLoop;
                }
            
            endOfLoop:
                // ...
            

            如您所见,我们现在有一个向后分支,这是我们的小循环。这里唯一仍然令人讨厌的是由于我们的break 语句而最终得到的分支。在某些情况下,我们可以以相同的方式移动它,但在其他情况下,它会一直存在。

            那么为什么编译器会这样做呢?好吧,如果我们可以展开循环,我们也许可以对其进行矢量化。我们甚至可以证明只是添加了常量,这意味着我们的整个循环可能会化为乌有。总结一下:通过使模式可预测(通过使分支可预测),我们可以证明某些条件在我们的循环中成立,这意味着我们可以在 JIT 优化期间发挥作用。

            但是,分支往往会破坏那些很好的可预测模式,因此优化人员不太喜欢这种情况。 Break、continue、goto——它们都打算打破这些可预测的模式——因此并不是真正的“好”。

            此时您还应该意识到,一个简单的foreach 比一堆到处乱跑的goto 语句更容易预测。就(1)可读性和(2)从优化器的角度来看,它都是更好的解决方案。

            另一件值得一提的是,它与优化编译器以将寄存器分配给变量非常相关(一个称为寄存器分配的过程)。您可能知道,您的 CPU 中只有有限数量的寄存器,它们是迄今为止硬件中最快的内存。在最内层循环中的代码中使用的变量更有可能被分配一个寄存器,而循环之外的变量则不太重要(因为此代码可能被命中较少)。

            求助,太复杂了……我该怎么办?

            最重要的是,您应该始终使用您可以使用的语言结构,这通常会(隐含地)为您的编译器构建可预测的模式。如果可能,尽量避免使用奇怪的分支(特别是:breakcontinuegotoreturn 中间什么都没有)。

            这里的好消息是,这些可预测的模式既易于阅读(对人类而言),又易于发现(对编译器而言)。

            其中一种模式称为 SESE,代表 Single Entry Single Exit。

            现在我们来解决真正的问题。

            想象一下你有这样的东西:

            // a is a variable.
            
            for (int i=0; i<100; ++i) 
            {
              for (int j=0; j<100; ++j)
              {
                 // ...
            
                 if (i*j > a) 
                 {
                    // break everything
                 }
              }
            }
            

            使其成为可预测模式的最简单方法是完全消除if

            int i, j;
            for (i=0; i<100 && i*j <= a; ++i) 
            {
              for (j=0; j<100 && i*j <= a; ++j)
              {
                 // ...
              }
            }
            

            在其他情况下,您也可以将方法拆分为 2 个方法:

            // Outer loop in method 1:
            
            for (i=0; i<100 && processInner(i); ++i) 
            {
            }
            
            private bool processInner(int i)
            {
              int j;
              for (j=0; j<100 && i*j <= a; ++j)
              {
                 // ...
              }
              return i*j<=a;
            }
            

            临时变量?好、坏还是丑?

            您甚至可以决定从循环中返回一个布尔值(但我个人更喜欢 SESE 形式,因为这是编译器会看到它的方式,而且我认为它更易于阅读)。

            有些人认为使用临时变量更干净,并提出这样的解决方案:

            bool more = true;
            for (int i=0; i<100; ++i) 
            {
              for (int j=0; j<100; ++j) 
              {
                 // ...
                 if (i*j > a) { more = false; break; } // yuck.
                 // ...
              }
              if (!more) { break; } // yuck.
              // ...
            }
            // ...
            

            我个人反对这种做法。再看一下代码是如何编译的。现在想想这将如何处理这些漂亮的、可预测的模式。得到图片?

            好吧,让我把它拼出来。会发生什么:

            • 编译器会将所有内容写成分支。
            • 作为优化步骤,编译器将进行数据流分析,以尝试删除仅在控制流中使用的奇怪more 变量。
            • 如果成功,变量more将从程序中删除,只保留分支。这些分支将被优化,因此您只会从内部循环中获得一个分支。
            • 如果不成功,变量more肯定会用在最内层循环中,所以如果编译器不优化它,它很有可能被分配给一个寄存器(这会占用宝贵的寄存器内存)。

            所以,总而言之:编译器中的优化器会遇到很多麻烦,以找出 more 仅用于控制流,并且在最佳情况下 会将其转换为外部 for 循环之外的单个分支。

            换句话说,最好的情况是它最终会得到这样的结果:

            for (int i=0; i<100; ++i) 
            {
              for (int j=0; j<100; ++j)
              {
                 // ...
                 if (i*j > a) { goto exitLoop; } // perhaps add a comment
                 // ...
              }
              // ...
            }
            exitLoop:
            
            // ...
            

            我个人对此的看法很简单:如果这是我们一直以来的意图,那么让我们让编译器和可读性的世界变得更容易,并立即编写。

            tl;博士:

            底线:

            • 如果可能,请在 for 循环中使用简单的条件。尽可能坚持使用您可以使用的高级语言结构。
            • 如果一切都失败了,而您只剩下gotobool more,请选择前者。

            【讨论】:

            • OTOH:我很少有打破外部循环的愿望或希望使用 goto-equiv(并且可以说很多代码可以写得更干净),尽管这在其他领域可能更常见..今天就是这样,但这主要是由于yield return。也就是说,虽然很容易展示一些有用的案例,但对于大多数“应用程序级”代码来说,C# 的挫败感可能非常低,不包括那些“刚刚来自”C/C++ 的案例;-) 我偶尔也会错过Ruby 的“抛出”(非异常展开)有时也适合这个领域。
            • @Suncat2000 只是引入更多代码,如变量和条件分支以避免使用goto,并不会使您的代码更具可读性 - 我认为发生的情况恰恰相反:实际上您控制流将包含更多节点——这几乎是复杂性的定义。仅仅避免它以帮助自律听起来在可读性方面适得其反。
            • @atlaste:对不起,让我更清楚地说明我的评论。我应该说的是:很好的解释。但是人们避免 goto 与性能无关。这是关于避免导致缺乏可读性的滥用。我们可以假设那些避开goto 的人只是没有纪律不滥用它。
            • @atlaste,“确认”是什么语言?
            • 对我来说,Goto 看起来很糟糕的原因是因为与所有其他代码不同,它带我们及时前进,到达一个我们还没有读过的地方,一个我们可能读到的神奇地方对此一无所知。在其他语言(如 powershell)中,它们具有 for/while 标签,以便您可以跳出已经输入的特定循环。 Goto 与现代语言所要实现的一切背道而驰。老实说,如果他们可以使用,那么为什么不这样编写我们的代码。 { ... } 如果 ( ... )?我想我们都明白为什么我们不这样做
            【解决方案19】:

            我就是这样做的。仍然是一种解决方法。

            foreach (var substring in substrings) {
              //To be used to break from 1st loop.
              int breaker=1;
              foreach (char c in substring) {
                if (char.IsLetter(c)) {
                  Console.WriteLine(line.IndexOf(c));
                  \\setting condition to break from 1st loop.
                  breaker=9;
                  break;
                }
              }
              if (breaker==9) {
                break;
              }
            }

            【讨论】:

              【解决方案20】:

              结束双循环最简单的方法是直接结束第一个循环

              string TestStr = "The frog jumped over the hill";
              char[] KillChar = {'w', 'l'};
              
              for(int i = 0; i < TestStr.Length; i++)
              {
                  for(int E = 0; E < KillChar.Length; E++)
                  {
                      if(KillChar[E] == TestStr[i])
                      {
                          i = TestStr.Length; //Ends First Loop
                          break; //Ends Second Loop
                      }
                  }
              }
              

              【讨论】:

              • 这不起作用,因为使用 break 您将结束内部循环。外层循环会继续迭代。
              【解决方案21】:

              可以使用循环中的自定义条件来打破循环,从而获得干净的代码。

                  static void Main(string[] args)
                  {
                      bool isBreak = false;
                      for (int i = 0; ConditionLoop(isBreak, i, 500); i++)
                      {
                          Console.WriteLine($"External loop iteration {i}");
                          for (int j = 0; ConditionLoop(isBreak, j, 500); j++)
                          {
                              Console.WriteLine($"Inner loop iteration {j}");
              
                              // This code is only to produce the break.
                              if (j > 3)
                              {
                                  isBreak = true;
                              }                  
                          }
              
                          Console.WriteLine("The code after the inner loop will be executed when breaks");
                      }
              
                      Console.ReadKey();
                  }
              
                  private static bool ConditionLoop(bool isBreak, int i, int maxIterations) => i < maxIterations && !isBreak;   
              

              使用此代码,我们获得以下输出:

              • 外部循环迭代 0
              • 内循环迭代 0
              • 内循环迭代 1
              • 内循环迭代 2
              • 内循环迭代 3
              • 内循环迭代 4
              • 内循环后的代码会在中断时执行

              【讨论】:

                【解决方案22】:

                最干净最短最可重用的方式是自调用匿名函数:

                • 没有转到
                • 没有标签
                • 没有临时变量
                • 没有命名函数

                使用匿名方法比最佳答案短一行。

                new Action(() =>
                {
                    for (int x = 0; x < 100; x++)
                    {
                        for (int y = 0; y < 100; y++)
                        {
                            return; // exits self invoked lambda expression
                        }
                    }
                })();
                Console.WriteLine("Hi");
                

                【讨论】:

                  【解决方案23】:

                  此处未提及的另一个既干净又不依赖于较新的 .NET 功能的选项是将双循环合并为产品上的单循环。然后在循环内部,可以使用简单的数学计算计数器的值:

                  int n; //set to max of first loop
                  int m; //set to max of second loop
                  
                  for (int k = 0; k < n * m; k++)
                  {
                      //calculate the values of i and j as if there was a double loop
                      int i = k / m;
                      int j = k % m;
                      
                      if(exitCondition)
                      {
                          break;
                      }
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2022-09-24
                    • 2019-04-19
                    • 1970-01-01
                    • 2014-06-28
                    • 2016-04-08
                    • 1970-01-01
                    • 2010-12-31
                    • 1970-01-01
                    相关资源
                    最近更新 更多