[C#] Perché una dichiarazione "continua" non può essere all'interno di un blocco "finally"?


Answers

Ecco una fonte affidabile:

Un'istruzione continue non può uscire da un blocco finally (Sezione 8.10). Quando un'istruzione continua si verifica all'interno di un blocco finally, la destinazione dell'istruzione continue deve trovarsi nello stesso blocco finally; in caso contrario, si verifica un errore in fase di compilazione.

È preso da MSDN, 8.9.2 La dichiarazione continua .

La documentazione dice che:

Le istruzioni di un blocco finally vengono sempre eseguite quando il controllo lascia una dichiarazione try. Questo è vero se il trasferimento del controllo avviene come risultato della normale esecuzione, come risultato dell'esecuzione di un'istruzione break, continue, goto o return, o come risultato della propagazione di un'eccezione dall'istruzione try. Se viene lanciata un'eccezione durante l'esecuzione di un blocco finally, l'eccezione viene propagata alla successiva dichiarazione try inclusa. Se un'altra eccezione era nel processo di propagazione, quell'eccezione viene persa. Il processo di propagazione di un'eccezione viene discusso ulteriormente nella descrizione dell'istruzione throw (Sezione 8.9.5).

È da qui 8.10 La dichiarazione try .

Question

Non ho un problema; Sono solo curioso. Immagina il seguente scenario:

foreach (var foo in list)
{
    try
    {
         //Some code
    }
    catch (Exception)
    {
        //Some more code
    }
    finally
    {
        continue;
    }
}

Questo non verrà compilato, poiché solleva l' errore del compilatore CS0157 :

Il controllo non può lasciare il corpo di una clausola finale

Perché?




Come altri hanno affermato, ma focalizzato sulle eccezioni, si tratta in realtà di una gestione ambigua del controllo del trasferimento.

Nella tua mente, stai probabilmente pensando a uno scenario come questo:

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

In teoria, puoi tracciare il flusso di controllo e dire, sì, questo è "ok". Nessuna eccezione viene lanciata, nessun controllo viene trasferito. Ma i progettisti di linguaggio C # avevano in mente altre questioni.

L'eccezione generata

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

Il ritorno

public static object ReturnSomething()
{
    foreach(var item in list)
    {
        try
        {
            return item;
        }
        finally
        {
            continue;
        }
    }
}

La rottura

public static void Break()
{
    foreach(var item in list)
    {
        try
        {
            break;
        }
        finally
        {
            continue;
        }
    }
}

Quindi, in conclusione, sì, mentre c'è una leggera possibilità di usare un continue in situazioni in cui il controllo non viene trasferito, ma un buon affare (maggioranza?) Di casi comporta eccezioni o blocchi di return . I progettisti di linguaggi ritenevano che sarebbe stato troppo ambiguo e (probabilmente) impossibile assicurare in fase di compilazione che il tuo continue venisse utilizzato solo nei casi in cui il flusso di controllo non viene trasferito.




Il blocco finally può essere eseguito con un'eccezione in attesa di essere ripubblicato. Non avrebbe davvero senso essere in grado di uscire dal blocco (da un continue o da qualsiasi altra cosa) senza ripensare all'eccezione.

Se vuoi continuare il tuo ciclo, qualunque cosa accada, non hai bisogno dell'ultima affermazione: prendi l'eccezione e non retrocedere.




"Questo non verrà compilato e penso che abbia senso"

Beh, penso che non sia così.

Quando hai letteralmente catch(Exception) allora non hai bisogno di finalmente (e probabilmente nemmeno di continue ).

Quando hai la catch(SomeException) più realistica catch(SomeException) , cosa dovrebbe accadere quando non viene rilevata un'eccezione? Il tuo continue vuole andare in un modo, l'eccezione gestirne un altro.




Tecnicamente parlando, si tratta di una limitazione del CIL sottostante. Dalla specifica del linguaggio :

Il trasferimento di controllo non è mai permesso di inserire un gestore catch o una clausola finally tranne che attraverso il meccanismo di gestione delle eccezioni.

e

Il trasferimento di controllo da un'area protetta è consentito solo attraverso un'istruzione di eccezione ( leave , end.filter , end.catch o end.finally )

Nella pagina del documento per l' istruzione br :

Controlli in entrata e in uscita da try, catch, filter e infine i blocchi non possono essere eseguiti da questa istruzione.

Questo ultimo vale per tutte le istruzioni di ramo, inclusi beq , brfalse , ecc.