Why can't a 'continue' statement be inside a 'finally' block?
C#.NetC# Problem Overview
I don't have a problem; I'm just curious. Imagine the following scenario:
foreach (var foo in list)
{
try
{
//Some code
}
catch (Exception)
{
//Some more code
}
finally
{
continue;
}
}
This won't compile, as it raises compiler error CS0157:
> Control cannot leave the body of a finally clause
Why?
C# Solutions
Solution 1 - C#
finally
blocks run whether an exception is thrown or not. If an exception is thrown, what the heck would continue
do? You cannot continue execution of the loop, because an uncaught exception will transfer control to another function.
Even if no exception is thrown, finally
will run when other control transfer statements inside the try/catch block run, like a return
, for example, which brings the same problem.
In short, with the semantics of finally
it doesn't make sense to allow transferring control from inside a finally
block to the outside of it.
Supporting this with some alternative semantics would be more confusing than helpful, since there are simple workarounds that make the intended behaviour way clearer. So you get an error, and are forced to think properly about your problem. It's the general "throw you into the pit of success" idea that goes on in C#.
If you want to ignore exceptions (more often than not is a bad idea) and continue executing the loop, use a catch all block:
foreach ( var in list )
{
try{
//some code
}catch{
continue;
}
}
If you want to continue
only when no uncaught exceptions are thrown, just put continue
outside the try-block.
Solution 2 - C#
Here is a reliable source:
> A continue statement cannot exit a finally block (Section 8.10). When > a continue statement occurs within a finally block, the target of the > continue statement must be within the same finally block; otherwise, a > compile-time error occurs.
It is taken from MSDN, 8.9.2 The continue statement.
The documentation say that:
> The statements of a finally block are always executed when control leaves a > try statement. This is true whether the control transfer occurs as a > result of normal execution, as a result of executing a break, > continue, goto, or return statement, or as a result of propagating an > exception out of the try statement. If an exception is thrown during > execution of a finally block, the exception is propagated to the next > enclosing try statement. If another exception was in the process of > being propagated, that exception is lost. The process of propagating > an exception is discussed further in the description of the throw statement (Section 8.9.5).
It is from here 8.10 The try statement.
Solution 3 - C#
You may think it makes sense, but it doesn't make sense actually.
foreach (var v in List)
{
try
{
//Some code
}
catch (Exception)
{
//Some more code
break; or return;
}
finally
{
continue;
}
}
What do you intend to do a break or a continue when an exception is thrown? The C# compiler team doesn't want to make decision on their own by assuming break
or continue
. Instead, they decided to complain the developer situation will be ambiguous to transfer control from finally block
.
So it is the job of developer to clearly state what he intends to do rather than compiler assuming something else.
I hope you understand why this doesn't compile!
Solution 4 - C#
As others have stated, but focused on exceptions, it's really about ambiguous handling of transferring control.
In your mind, you're probably thinking of a scenario like this:
public static object SafeMethod()
{
foreach(var item in list)
{
try
{
try
{
//do something that won't transfer control outside
}
catch
{
//catch everything to not throw exceptions
}
}
finally
{
if (someCondition)
//no exception will be thrown,
//so theoretically this could work
continue;
}
}
return someValue;
}
Theoretically, you can track the control flow and say, yes, this is "ok". No exception is thrown, no control is transferred. But the C# language designers had other issues in mind.
The Thrown Exception
public static void Exception()
{
try
{
foreach(var item in list)
{
try
{
throw new Exception("What now?");
}
finally
{
continue;
}
}
}
catch
{
//do I get hit?
}
}
The Dreaded Goto
public static void Goto()
{
foreach(var item in list)
{
try
{
goto pigsfly;
}
finally
{
continue;
}
}
pigsfly:
}
The Return
public static object ReturnSomething()
{
foreach(var item in list)
{
try
{
return item;
}
finally
{
continue;
}
}
}
The Breakup
public static void Break()
{
foreach(var item in list)
{
try
{
break;
}
finally
{
continue;
}
}
}
So in conclusion, yes, while there is a slight possibility of using a continue
in situations where control isn't being transferred, but a good deal (majority?) of cases involve exceptions or return
blocks. The language designers felt this would be too ambiguous and (likely) impossible to ensure at compile time that your continue
is used only in cases where control flow is not being transferred.
Solution 5 - C#
In general continue
does not make sense when used in finally
block. Take a look at this:
foreach (var item in list)
{
try
{
throw new Exception();
}
finally{
//doesn't make sense as we are after exception
continue;
}
}
Solution 6 - C#
> "This won't compile and I think it makes complete sense"
Well, I think it doesn't.
When you literally have catch(Exception)
then you don't need the finally (and probably not even the continue
).
When you have the more realistic catch(SomeException)
, what should happen when an exception is not caught? Your continue
wants to go one way, the exception handling another.
Solution 7 - C#
You cannot leave the body of a finally block. This includes break, return and in your case continue keywords.
Solution 8 - C#
The finally
block can be executed with an exception waiting to be rethrown. It wouldn't really make sense to be able to exit the block (by a continue
or anything else) without rethrowing the exception.
If you want to continue your loop whatever happens, you do not need the finally statement: Just catch the exception and don't rethrow.
Solution 9 - C#
finally
runs whether or not an uncaught exception is thrown. Others have already explained why this makes continue
illogical, but here is an alternative that follows the spirit of what this code appears to be asking for. Basically, finally { continue; }
is saying:
- When there are caught exceptions, continue
- When there are uncaught exceptions, allow them to be thrown, but still continue
(1) could be satisfied by placing continue
at the end of each catch
, and (2) could be satisfied by storing uncaught exceptions to be thrown later. You could write it like this:
var exceptions = new List<Exception>();
foreach (var foo in list) {
try {
// some code
} catch (InvalidOperationException ex) {
// handle specific exception
continue;
} catch (Exception ex) {
exceptions.Add(ex);
continue;
}
// some more code
}
if (exceptions.Any()) {
throw new AggregateException(exceptions);
}
Actually, finally
would have also executed in the third case, where there were no exceptions thrown at all, caught or uncaught. If that was desired, you could of course just place a single continue
after the try-catch block instead of inside each catch
.
Solution 10 - C#
Technically speaking, it's a limitation of the underlying CIL. From the language spec:
> Control transfer is never permitted to enter a catch handler or finally clause except through the exception handling mechanism.
and
> Control transfer out of a protected region is only permitted through an exception instruction (leave
, end.filter
, end.catch
, or end.finally
)
On the doc page for the br
instruction:
> Control transfers into and out of try, catch, filter, and finally blocks cannot be performed by this instruction.
This last holds true for all branch instructions, including beq
, brfalse
, etc.
Solution 11 - C#
The designers of the language simply didn't want to (or couldn't) reason about the semantics of a finally block being terminated by a control transfer.
One issue, or perhaps the key issue, is that the finally
block gets executed as part of some non-local control transfer (exception processing). The target of that control transfer isn't the enclosing loop; the exception processing aborts the loop and continues unwinding further.
If we have a control transfer out of the finally
cleanup block, then the original control transfer is being "hijacked". It gets canceled, and control goes elsewhere.
The semantics can be worked out. Other languages have it.
The designers of C# decided to simply disallow static, "goto-like" control transfers, thereby simplifying things somewhat.
However, even if you do that, it doesn't solve the question of what happens if a dynamic transfer is initiated from a finally
: what if the finally block calls a function, and that function throws? The original exception processing is then "hijacked".
If you work out the semantics of this second form of hijacking, there is no reason to banish the first type. They are really the same thing: a control transfer is a control transfer, whether it the same lexical scope or not.