When to use the 'continue' keyword in C# [closed]
Asked Answered
R

9

38

Recently, I was going through an open-source project and although I have been developing for several years in .NET, I hadn't stumbled across the continue keyword before.

Question: What are some best practices or areas that would benefit from using the continue keyword? Is there a reason I might not have seen it previously?

Rushing answered 1/11, 2011 at 21:25 Comment(1)
Since 3.5, people use LINQ Where clauses to handle situations that used to be solved with continue.Debunk
Z
60

You use it to immediately exit the current loop iteration and begin the next, if applicable.

foreach (var obj in list)
{
    continue;

    var temp = ...; // this code will never execute
}

A continue is normally tied to a condition, and the condition could usually be used in place of the continue;

foreach (var obj in list)
{ 
    if (condition)
       continue;

    // code
} 

Could just be written as

foreach (var obj in list)
{
    if (!condition)
    {
        // code
    }
}

continue becomes more attractive if you might have several levels of nested if logic inside the loop. A continue instead of nesting might make the code more readable. Of course, refactoring the loop and the conditionals into appropriate methods would also make the loop more readable.

Zeniazenith answered 1/11, 2011 at 21:27 Comment(1)
what about cyclomatic complexity as my compiler suggests? isnt it better to not nest code if one can help it?Labret
G
20

The continue keyword is used to skip the rest of the loop block and continue on. For example:

for(int i = 0; i < 5; i++)
{
   if(i == 3) continue; //Skip the rest of the block and continue the loop

   Console.WriteLine(i);
}

Will print:

0
1
2
4
Garrulous answered 1/11, 2011 at 21:27 Comment(0)
S
14

It prevents deep nesting.

foreach(var element in collection)
{
    doSomething();      
    doSomethingElse();
    if (condition1)
    {
        action1();
        action2();
        if (condition2)
        {
           action3();                            
        }
    }
}

could be rewritten as

foreach(var element in collection)
{
    doSomething();      
    doSomethingElse();
    if (!condition1)
    {
       continue;
    }
    action1();
    action2();
    if (!condition2)
    {
       continue;
    }
    action3();
}

If the code blocks are not trivial but vertically bigger, the use of continue may improve code readibility. Obviously it should be used with consideration, like every other language construct.

Stipend answered 1/11, 2011 at 21:29 Comment(2)
+1 for answering the best practice part. I really hate those page long if-blocks.Diondione
It's actually for early exiting of that iteration of the loop (exit early principle), continue has nothing to do with nesting if statements. If you have to do that to any great extent to avoid nesting your code needs refactoring. This has been one of two major causes off long-term bugs within > 6 legacy projects I have been contracted to work on over the years.Cyclopropane
T
12

When you don't want to break out of the loop, but you want the next iteration:

for (int i = 0; i < something; i++)
{
    if (condition)
        continue;

    // expensive calculations, skip due to continue
    // or due to the condition above I don't want to 
    // run the following code
    stuff();
    code();
}
Television answered 1/11, 2011 at 21:26 Comment(0)
S
8

You should use it sparingly.

The best (= easiest to read) loops do not use break or continue, they are a kind of structured goto statements.

Having said that, 1 or even 2 break/continue statements won't make a loop unreadable, but it pays to make their use clear and to keep it simple.

Symptom answered 1/11, 2011 at 21:28 Comment(2)
The problem with goto is that it allows arbitrary jumps to any point in the code. continue re-starts the closest enclosing loop. I don't think the criticisms against goto apply. If they do, they also apply to return and throw.Upon
@recursive: this (much reduced) criticism certainly applies to return (preferred: a method should have 1 exit point). Exceptions are an entirely different matter.Symptom
I
8

Basically continue and break are better (but often just disguised) goto statements...

Whenever you are inside a loop and know that everything coming next inside the loop should be skipped and continued with the next iteration you could use continue...

As such they should be used seldomn... sometimes they make code very readable and clear (if the alternative would be several levels of nesting for example)... most of the time they add some confusion similar to goto.

Individualist answered 1/11, 2011 at 21:30 Comment(0)
L
6

My guess as to why you haven't seen it previously is that continue is sort of a cousin to goto, break, and early returns from functions. And we all know that Goto is considered harmful, so many developers probably tend to avoid it.

For me, I tend to use continue when I want to clean up a loop where there might be some values that I don't care about. Using continue I can skip those values without enclosing the "important" logic in my loop in a nested if.

foreach (var v in GetSomeValues())
{
  if (ThisValueIsNotImportant(v)) continue;

  //Do important stuff with the value.
}
Lakh answered 1/11, 2011 at 21:31 Comment(0)
E
2

Think of it as a "return", but only applicable to the context of a loop. A common example is a state machine, looping over all available input.

while(!SomeQueue.Empty)
{
    byte nextByte = SomeQueue.Dequeue();

    switch State:
    {
        case A:
            if(nextByte == Condition)
            {
                State = B;
            }
            else                
            {
                State = ParseError;
            }
            continue;
        case B:
            //Test nextByte
            State = C;
            continue;
        case C:
            //Test nextByte
            State = A;
            continue;
        case ParseError:
            //Do something for this condition
            State = A;
            continue;
}
Etom answered 1/11, 2011 at 21:32 Comment(1)
The continue in this case is completely redundant as the processes occur based on the switch and then loop anyway. It also doesn't 'return' in the context of a loop as that would be a break (exiting the loop), it simply goes to the next item in the enumerable.Cyclopropane
C
0

Several big reasons for not implementing continue (or refactoring to remove it whenever possible)

  1. Your loop is inefficient - If you are using continue, then the loop you are using is processing elements in the list it does not need to look at. Now with Jitted compilers or VM systems like Java, C# the compiler will often simplify the and refactor the list when it compiles. But you may still rack up the memory as additional objects may be stored. Lower level languages will show a huge hit. Refactor your loop statement to only loop through only elements that need to be affected.
  2. You are changing where the responsibility/logic of the loop lies - A loop is defined by it's declaration, this is where all control of the loop should be. By using continue (and to a lesser extent 'break') you are making 2 different lines responsible for the loop logic, worse: the continue could be anywhere inside the code inside the loop (see next point).
  3. You are making it harder for any code changes to occur and may introduce bugs - This may not seem obvious with tutorials and other simple examples but even with only 10 -20 lines of code in the loop this WILL cause problems on production code. Lets say you have a loop through objects and you look at a param on the object to determine if you want to continue at a certain point. Now when that object is changed in the future and that param may be altered, you not only have to look at any loop logic to make sure it is updated, but you already need to know a continue is used in the loop, and find it and alter it as well. With even only 10-20 lines of code inside a loop, this can be missed easily, leading to a situation where the person updating the code knows they checked the loop logic and goes hunting for the inevitable bug elsewhere.

Some Examples:

Continue used unnecessarily looping through unneeded items:

foreach(var item in list) {
    if(item.param3 == "hippies") continue;
    //edit the item here
}

Refactored (uses less memory & cpu):

foreach(var item in list.Where(x => x.param3 != "hippies")) {
    //edit item here
}

continue used to only update some parts of a list item.

for(var item in itemList) { //must always be checked with any object change
    item.param1 = 45;
    item.param2 = "this is a thing";
    if(item.param3 == "hippies") continue; //must be checked with any object code change
    item.param3 = "dodgy code"
    DoThisCode(item);
}

Now imagine the above example if item.param3 is altered in the object code to never be (or is always) equal to "hippies", imagine this with with 20 - 50 lines of code in the loop (bad practice but it happens) with a continue dumped into the middle, or even worse imagine a newParam is added, and the loop should now do something only when newParam meets a certain condition and param3 is not equal to hippies. The person doing the update has to know that continue is in use and update it correctly as well as check the loop conditional and make sure it is good even though it may state the opposite of what they are looking for (param3 == "hippies" instead of != "hippies" or another set of values)

Last example (based on a real project developed in 2018 I had to update, this is massively simplified, the real loop code was 42 lines long)

foreach(var item in list) {
    if(item.param1 == condition) 
    {
        someChangesMadeToItem(item);
    }
    else if(item.param3 == condition)
    {
        someMoreChangesMadeToItem(item);
    }
    //another 5-6 lines of code.

    if(item.param2 == condition) {
        continue;
    }

    someMoreChangesMadeToItem(item);
    //another 20+ lines of code here
    result.add(item);
}

return result;

In the above example another param was added to the object that was supposed to disallow adding to the results (replacing the prior logic).

So not only was the loop processing unnecessary items, it was executing multiple lines of code on those items when they were never output (as they were never added to the result), and when updated I updated the loop logic with a .Where(x => x.NewParam condition) and missed the continue in the middle of the code, this led to most of the output being correct, but some items being missed for no immediately apparent reason (where the old conditional for the continue held true) and only by stepping through the code and checking the list did I see the conditional as there were multiple function calls within the loop and the issue could have been occurring in any of them.

Cyclopropane answered 30/3, 2022 at 23:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.