简体   繁体   English

如何从嵌套方法“继续” ForEach循环?

[英]How do I 'continue' a ForEach loop from a nested method?

I have a ForEach loop that processes a rather large list of contacts. 我有一个ForEach循环,可以处理大量联系人。 Instead of doing a bunch of processing in this main loop, I call a method which in turn calls a method, etc. Methods call other methods in other classes, maybe other namespaces. 我没有在这个主循环中进行大量处理,而是调用了一个方法,该方法又调用了方法等。方法调用其他类中的其他方法,可能是其他名称空间。

How do I break from the current method if a condition is detected and I want to move on to the next contact? 如果检测到条件并且想要继续下一个联系人,如何从当前方法中脱颖而出? I'm in a method a few levels from the main ForEach loop. 我在ForEach主循环中处于几个级别的方法中。

Typically, you could use the continue keyword inside a ForEach to skip to the next element in the collection. 通常,您可以在ForEach中使用continue关键字来跳到集合中的下一个元素。 continue is not an option in this case. 在这种情况下, continue是不可行的。 When I type continue , it gets underlined in red with a "Unresolved Message" comment. 当我键入continue ,它会以红色下划线显示,并带有“未解决的消息”注释。

So, what do I do? 那么,我该怎么办?

You're going down a bad path here; 您在这里走的路很糟; take a step back and reconsider your design. 退后一步,重新考虑您的设计。

In general it is a really bad idea to have methods that attempt to influence the control flow of their callers. 通常,拥有尝试影响其调用者控制流的方法是一个非常糟糕的主意。 A method is the servant of the caller, not the master. 方法是调用者的仆人,而不是主控者。 The method doesn't decide what the caller does next; 该方法不决定调用者下一步要做什么; that's not its business. 这不是它的事。 Rather, a method: 而是一种方法:

  • performs a calculation and returns a result, or 执行计算并返回结果,或者
  • modifies some state, and then 修改一些状态,然后
  • throws an exception if the operation could not be completed successfully 如果无法成功完成操作,则会引发异常

There are advanced control flow styles in which callees work together with callers to determine "what happens next" - Continuation Passing Style, for instance. 有高级控制流样式,在这种样式中,被叫方与主叫方一起确定“下一步会发生什么”,例如Continuation Passing Style。 But you shouldn't go there. 但是你不应该去那里。 They are very difficult to understand. 他们很难理解。

Your supporting methods should probably return a value which is checked in the for-loop of the main method, and then continue from there if that's what the value indicates. 您的支持方法应该返回一个值,该值在main方法的for循环中进行检查,然后从那里continue (如果该值指示该值)。

Using exceptions is another alternative, but they're generally considered slower-- I'm not sure about C# in particular. 使用异常是另一种选择,但是通常认为它们较慢-我不确定C#。 They're also usually considered bad form when used in this manner. 当以这种方式使用它们时,它们通常也被认为是不好的形式。 Exceptions should be thrown in exceptional situations, not as a normal part of flow control. 异常情况下应抛出异常 ,而不是将其作为流控制的正常部分。 There are arguably situations where it's ok to use them in this manner, such as the web framework Play!, but you're probably not in one of them. 可以说,在某些情况下可以以这种方式使用它们,例如Web框架Play !,但是您可能不在其中之一。

you could maybe have a flag... something like bool conditionDetected and when a condition is detected you just set it to true and the have if (conditionDetected) return; 您可能有一个标志……类似bool conditionDetected ,当检测到条件时,只需将其设置为true并if (conditionDetected) return; from the methods till you get to the top where you if( conditionDetected) continue to the next one...then you set it again to false and carry on... you get an error because you arent inside of the foreach loop when you move to another method 从方法直到到达顶部, if( conditionDetected) continue到下一个...然后将其再次设置为false并继续...会出现错误,因为当您在foreach循环中找不到转到另一种方法

There is no simple way to accomplish this. 没有简单的方法可以做到这一点。 The only place where you can force the next iteration of the foreach loop is from directly within it's body. 可以强制foreach循环的下一个迭代的唯一位置是直接在其体内进行。 Hence to do this you must get out of the method and back into the body of the foreach loop. 因此,要做到这一点,您必须退出方法并回到foreach循环的主体中。

There are a couple of ways to achieve this 有两种方法可以实现此目的

  • Throw an exception: Please, please don't do this. 引发异常:请,请不要这样做。 Exceptions shouldn't be used as a control flow mechanism 异常不应用作控制流机制
  • Exit the method, and all methods between you and the foreach loop with a return code that causes the body to execute a continue statement 退出该方法以及您与foreach循环之间的所有方法,并返回一条代码,该代码使主体执行continue语句

If I'm understanding your problem correctly, you have a for loop like so: 如果我正确地理解了您的问题,那么您会有一个for循环,如下所示:

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

DoComplexProcessing then calls another method, which calls another method and so on. 然后, DoComplexProcessing调用另一个方法,该方法又调用另一个方法,依此类推。

Once you're down, say, 4 levels, you detect a condition (whatever it is) and want to abort that iteration of your DoComplexProcessing . 一旦下降了4个级别,就可以检测到某种情况(无论发生什么情况),并希望中止DoComplexProcessing该迭代。

Presuming that's right, what I would do is have an object that rides along with the method chain as an out parameter At each level, once the "bad" condition is found, I would return null (or some other default value when null isn't an option) and set the reference object to a state that means 'abort.' 假设是正确的,我要做的是将一个对象与方法链一起作为out参数使用。在每个级别,一旦找到“坏”条件,我将返回null(或当null为空时返回其他一些默认值) (一个选项)并将参考对象设置为表示“中止”的状态。 Each method would then check for that 'abort' state and then do the same "return null, set object to 'abort'" call. 然后,每个方法将检查该“异常终止”状态,然后执行相同的“返回null,将对象设置为”异常终止””调用。

Something like this: 像这样:

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

the next method might do this 下一个方法可能会这样做

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;
}
}

One solution would be to throw a custom exception which would bubble up and finally be caught by your foreach loop. 一种解决方案是抛出一个自定义异常,该异常会冒泡并最终被您的foreach循环捕获。

Ensure that you're using a custom exception (ie. have it's own type and not just using a catch(Exception) statement), this way you know that you're definitely catching the correct one. 确保您使用的是自定义异常(即具有它自己的类型,而不仅仅是使用catch(Exception)语句),这样您就知道您肯定在捕获正确的catch(Exception)

In the catch block, simply continue along with your foreach loop (or handle appropriately). catch块中,只需继续执行foreach循环(或适当地处理)。

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

If it's failing for a specific reason, you're best naming the exception appropriately, this way you don't have an exception type just for controlling the flow of your application, and it's actually meaningful. 如果由于特定原因而失败,则最好适当地命名该异常,这样,您就不会具有仅用于控制应用程序流的异常类型,这实际上是有意义的。 In the future you may wish to handle this. 将来您可能希望解决此问题。


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);
}

To extend on Erics answer, the solution is to refactor your loops so that the outer loop has more control and influence over long-running methods that it calls. 为了扩展对Erics的回答,解决方案是重构循环,以便外部循环对其调用的长时间运行的方法具有更多的控制和影响。

For example suppose you have "skip" and "cancel" buttons that let the user either skip a contract, or cancel processing entirely - you could strucutre your code a little like this: 例如,假设您具有“跳过”和“取消”按钮,这些按钮使用户要么跳过合同,要么完全取消处理-您可以像下面这样构造代码:

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;
        }
    }
}

(There is obviously a lot of fleshing out to be done here) (显然,这里有很多工作要做)

The idea is that if the control of processing is important to you, then the methods and classes responsible for that processing should have mechanisms for control "baked in". 这个想法是,如果处理的控制对您很重要,那么负责该处理的方法和类应具有控制机制。 Having an entire class responsible for the processing if often a nice way to encapsulate this - it also makes it really easy to do things like reporting progress. 让整个类负责处理(如果通常)是封装此内容的好方法-这也使执行报告进度之类的事情变得非常容易。

The above lets any method of the ContactProcessor detect if it should skip processing (no exceptions involved!), and set the Skip flag. 上面的方法允许ContactProcessor任何方法检测是否应跳过处理(不涉及任何异常!),并设置Skip标志。 It also potentially lets the outer loop set the Skip flag (for example based on user input). 它还可能使外部循环设置“ Skip标志(例如,基于用户输入)。

to use continue you need to be directly inside the loop, so you need to be aware of the break in the loop, but couldn't you simply return a bool from the methods? 要使用继续,您需要直接在循环内部,因此您需要知道循环中的中断,但是您不能简单地从方法中返回布尔值吗?

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
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM