简体   繁体   中英

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

I have a ForEach loop that processes a rather large list of contacts. 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.

Typically, you could use the continue keyword inside a ForEach to skip to the next element in the collection. continue is not an option in this case. When I type continue , it gets underlined in red with a "Unresolved Message" comment.

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. 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.

Using exceptions is another alternative, but they're generally considered slower-- I'm not sure about C# in particular. 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.

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

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. Hence to do this you must get out of the method and back into the body of the foreach loop.

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

If I'm understanding your problem correctly, you have a for loop like so:

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

DoComplexProcessing then calls another method, which calls another method and so on.

Once you're down, say, 4 levels, you detect a condition (whatever it is) and want to abort that iteration of your 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.' Each method would then check for that 'abort' state and then do the same "return null, set object to 'abort'" call.

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.

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.

In the catch block, simply continue along with your foreach loop (or handle appropriately).

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.

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. It also potentially lets the outer loop set the Skip flag (for example based on user input).

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
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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