DenaliHardtail
DenaliHardtail

Reputation: 28336

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?

Upvotes: 16

Views: 11678

Answers (8)

Justin
Justin

Reputation: 86789

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

Upvotes: 1

Eric Lippert
Eric Lippert

Reputation: 660503

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.

Upvotes: 27

Rudi Visser
Rudi Visser

Reputation: 22019

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

Upvotes: 1

AllenG
AllenG

Reputation: 8190

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

Upvotes: 2

JaredPar
JaredPar

Reputation: 755467

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

Upvotes: 2

faester
faester

Reputation: 15086

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
}

Upvotes: 0

Ivan Crojach Karačić
Ivan Crojach Karačić

Reputation: 1911

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

Upvotes: 4

Brad Mace
Brad Mace

Reputation: 27916

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.

Upvotes: 6

Related Questions