Quick response:
foreach(//do some stuff) { foreach(//do some stuff) { if(//check some condition) { goto end; // I'd probably add a comment here } } // *1 } end: {} // the rest of your code.
But, but SESE ...
SESE violations are violations of the Single Single Single Exit principle. This is fairly easy to fix using an additional condition:
bool found = false; for (int i=0; i<foo.Count && !found; ++i) { for (int j=0; j<bar.Count; ++j) { if (...) { found = true; } }
So why use goto here?
I believe that creating proper, supported code means that you use language constructs that most accurately describe your intentions. The "code" here always consists of two things:
- A control stream expressed through things like
for , while , break and goto . - A stream of data that is expressed through expressions, variables, and other memory access.
The goal of an OP is to break out of a nested loop, which is equivalent to a control flow operation. Therefore, I believe that you should use the control flow operation, which most accurately reflects that the intention is in this case a goto .
Note that this is not at all the reason you should be abused to introduce goto instructions all over the place; if you do, the code will become very difficult to read, which has nothing to do with maintainability and readability. You should consider the goto as the "last control thread" goto , which is very rarely used in properly processed code.
In this case, this means that you should not create local variables to control the flow of control, unless absolutely necessary (for example, if there is no language construct that can clearly express your intention). For the same reason, I would not use Linq in this particular scenario.
I want performance. What should I do?
I believe that most abuse of language constructs stems from the fact that they donβt understand how the compiler deals with code, so I'm used to explaining parts of how it works internally. Keep in mind that I recommend using goto because it most clearly describes your intentions, and not because it can be any faster. Here:
Imagine that you are a compiler. You have a ton of code at * 1 in your code and cannot use return . Now there are two options:
- You can use goto.
- You can use an additional flag.
Option 1 will be compiled into a field with memory. Memory is βfailedβ in the sense that compilers will do everything possible to consume as little memory as possible, preferably in registers. Where your work comes from. Thus, the compiler will try to eliminate the flag.
To do this, your compiler will do a ton of flow analysis and other things, trying to determine that there are actually two code paths: one when the flag is set, and the other if not.
Now, if you're lucky, the compiler will have its βahaβ moment and change your code from (2) to a simple GOTO, in which case the sky will still be blue and everyone will be happy.
However, if you are out of luck (and there are plenty of practical reasons for this), he will not detect this from flow analysis and will not create a GOTO. Since your flag is used in the inner loop, it may even allocate a register for this, which may be the worst case scenario.
If you were to use goto , there is no need for this in the first place. You just give the compiler the right solution. Plain.
Do you have more details on how the compiler does this?
Yes, take a look at this 2-hour Chandler video that explains how compilers work: https://www.youtube.com/watch?v=FnGCDLhaxKU
-updated - Apparently, some people misinterpreted my story, as @Groo observed. I made some adjustments to clarify what I wanted to say.