【问题标题】:How do I 'continue' a ForEach loop from a nested method?如何从嵌套方法“继续” ForEach 循环?
【发布时间】:2011-05-05 21:07:32
【问题描述】:

我有一个处理相当大的联系人列表的 ForEach 循环。我没有在这个主循环中进行大量处理,而是调用一个方法,该方法又调用一个方法等。方法调用其他类中的其他方法,也许是其他命名空间。

如果检测到条件并且我想继续下一个联系人,我该如何中断当前方法?我的方法距离主 ForEach 循环只有几个级别。

通常,您可以在 ForEach 中使用 continue 关键字来跳到集合中的下一个元素。在这种情况下,continue 不是一个选项。当我输入continue 时,它会在红色下划线并带有“未解决的消息”注释。

那么,我该怎么办?

【问题讨论】:

  • 能否提供代码示例?

标签: c# c#-4.0


【解决方案1】:

你在这里走的是一条坏路;退后一步,重新考虑您的设计。

一般来说,让方法试图影响调用者的控制流是一个非常糟糕的主意。方法是调用者的仆人,而不是主人。该方法不决定调用者下一步做什么;那不关它的事。而是一种方法:

  • 执行计算并返回结果,或
  • 修改一些状态,然后
  • 如果操作无法成功完成,则引发异常

有一些高级控制流样式,其中被调用者与调用者一起确定“接下来会发生什么” - 例如,继续传递样式。但你不应该去那里。他们很难理解。

【讨论】:

    【解决方案2】:

    您的支持方法可能应该返回一个在 main 方法的 for 循环中检查过的值,然后从那里返回 continue(如果该值表示的内容)。

    使用异常是另一种选择,但它们通常被认为速度较慢——我不确定 C# 尤其如此。当以这种方式使用时,它们通常也被认为是不好的形式。应该在异常情况下抛出异常,而不是作为流控制的正常部分。可以说在某些情况下以这种方式使用它们是可以的,例如 Web 框架 Play!,但您可能不在其中之一。

    【讨论】:

    • 这是 Monad 风格的流控制 ;-)
    【解决方案3】:

    你可能有一个标志......像bool conditionDetected 这样的东西,当检测到一个条件时,你只需将它设置为true,然后从方法中使用if (conditionDetected) return;,直到你到达if( conditionDetected) continue 的顶部下一个...然后您再次将其设置为 false 并继续...您会收到一个错误,因为当您移动到另一个方法时您不在 foreach 循环内

    【讨论】:

      【解决方案4】:

      没有简单的方法可以做到这一点。您可以强制执行foreach 循环的下一次迭代的唯一地方是直接在它的主体内。因此,要做到这一点,您必须退出该方法并返回到 foreach 循环的主体。

      有几种方法可以实现这一点

      • 抛出异常:请不要这样做。不应将异常用作控制流机制
      • 退出方法,以及您和foreach 循环之间的所有方法,返回码会导致主体执行continue 语句

      【讨论】:

        【解决方案5】:

        如果我正确理解了您的问题,那么您有一个这样的 for 循环:

        for(int i = 0; i < 100; i++)
        {
          DoComplexProcessing();
        }
        

        DoComplexProcessing 然后调用另一个方法,该方法又调用另一个方法,依此类推。

        一旦您下降了 4 个级别,您就会检测到一个条件(无论它是什么)并希望中止您的 DoComplexProcessing 的迭代。

        假设这是对的,我要做的是让一个对象与方法链一起作为out 参数在每个级别,一旦找到“坏”条件,我将返回 null(或其他一些默认值)当 null 不是选项时的值)并将引用对象设置为表示“中止”的状态。然后,每个方法都会检查该“中止”状态,然后执行相同的“返回 null,将对象设置为 '中止'”调用。

        类似这样的:

        TracerObject tracer = new tracer("good");
        for(int i = 0; i < 100; i++)
        {
          DoComplexProcessing(out tracer)
          if(tracer.status == "abort") DoSomethingElse()
        }
        

        下一个方法可能会这样做

        DoComplexProcessing(out TracerObject tracer)
        {
           var myObject = new MyObject()
           myObject.Property = DoSlightlyLessComplexProcessing(myObject, out tracer)
           if(tracer.Status == "abort")
           {
             //set myObject.Property to some default value
           }
           return myObject;
        }
        }
        

        【讨论】:

          【解决方案6】:

          一种解决方案是抛出一个自定义异常,该异常会冒泡并最终被您的foreach 循环捕获。

          确保您使用的是自定义异常(即拥有自己的类型,而不仅仅是使用 catch(Exception) 语句),这样您就知道您肯定捕捉到了正确的异常。

          catch 块中,继续执行您的 foreach 循环(或适当处理)。

          try {
              MethodWithIteration(i);
          } catch (ProcessingFailedException) {
              continue;
          }
          

          如果由于特定原因而失败,您最好适当地命名异常,这样您就不会有一个异常类型来控制应用程序的流程,而且它实际上是有意义的。将来您可能希望处理此问题。


          foreach(DoSomethingWithMe doSomething in objList)
          {
              // Option 1 : Custom Processing Exception
              try
              {
                  ProcessWithException(doSomething);
              } catch(ProcessingFailedException)
              {
                  // Handle appropriately and continue
                  // .. do something ..
                  continue;
              }
          
              // Option 2 : Check return value of processing
              if (!ProcessWithBool(doSomething))
                  continue;
          
              // Option 3 : Simply continue on like nothing happened
              // This only works if your function is the only one called (may not work with deeply-nested methods)
              ProcessWithReturn(doSomething);
          }
          

          【讨论】:

          • Ewwww... 流量控制的异常很糟糕
          • 我添加了半免责声明 ;-)
          【解决方案7】:

          要扩展 Eric 的答案,解决方案是重构循环,以便外部循环对其调用的长时间运行的方法具有更多控制和影响。

          例如,假设您有“跳过”和“取消”按钮,可以让用户跳过合同或完全取消处理 - 您可以像这样构建代码:

          foreach (var contact in contacts)
          {
              if (Cancel)
              {
                  return;
              }
              ContactProcessor processor = new ContactProcessor(contact);
              processor.Process(contact);
          }
          
          class ContactProcessor
          {
              public bool Skip { get; set; }
          
              private readonly Contact contact;
              public ContactProcessor(Contact contact)
              {
                  this.contact = contact;
              }
          
              public void Process()
              {
                  if (!this.Skip)
                  {
                      FooSomething();
                  }
                  if (!this.Skip)
                  {
                      BarSomething();
                  }
                  // Etc...
              }
          
              publiv void BarSomething()
              {
                  // Stuff goes here
                  if (this.contact.WTF())
                  {
                      this.Skip = true;
                  }
              }
          }
          

          (这里显然有很多充实的地方)

          这个想法是,如果处理的控制对您很重要,那么负责该处理的方法和类应该具有“嵌入”控制机制。让整个类负责处理,这通常是一种很好的封装方式——它也使得报告进度等事情变得非常容易。

          上面让ContactProcessor 的任何方法检测它是否应该跳过处理(不涉及异常!),并设置Skip 标志。它还可能让外部循环设置Skip 标志(例如基于用户输入)。

          【讨论】:

            【解决方案8】:

            要使用 continue 你需要直接在循环内,所以你需要知道循环中的中断,但你不能简单地从方法中返回一个布尔值吗?

            int[] ints = {1,2,3,4,5,6};
            
            foreach (var k in ints)
            {
              if (Continue(k))
              {
                   continue;
              }
            }
            
            
            bool Continue(int k)
            {
                return what's suitable
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2015-05-27
              • 2015-04-16
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2022-08-09
              相关资源
              最近更新 更多