108

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?

4

11 回答 11

151

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

C#, you, and the out if success

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.

于 2013-08-01T10:15:12.010 回答
32

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.

于 2013-08-01T10:10:36.147 回答
31

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!

于 2013-08-01T10:14:17.003 回答
16

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.

于 2013-08-01T10:40:10.400 回答
11

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;
    }
}
于 2013-08-01T10:12:06.037 回答
5

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

于 2013-08-01T10:13:45.893 回答
3

You cannot leave the body of a finally block. This includes break, return and in your case continue keywords.

于 2013-08-01T10:12:06.887 回答
3

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.

于 2013-08-01T10:15:34.307 回答
1

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:

  1. When there are caught exceptions, continue
  2. 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.

于 2013-08-01T14:18:57.457 回答
1

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.

于 2013-08-01T20:07:13.313 回答
-1

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.

于 2013-08-01T18:25:07.137 回答