Por que uma instrução 'continue' não pode estar dentro de um bloco 'finalmente'?


107

Eu não tenho um problema; Eu só estou curioso. Imagine o seguinte cenário:

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

Isso não compila, pois gera o erro do compilador CS0157 :

O controle não pode deixar o corpo de uma cláusula finalmente

Por quê?


7
Assim. Apenas curioso. Se faz sentido para você por que não compila, por que você quer que alguém explique o que já faz sentido? =)
J. Steen

14
por que você precisa de um continue;em finallybloco? não é o mesmo que continue;depois do try -- catchbloco?
bansi

6
@ J.Steen Não, SEI que finally/continueé uma limitação do compilador C # :-) Também estou curioso sobre o motivo dessa limitação.
xanatos de

5
@xanatos - Tecnicamente falando, é uma limitação do CIL subjacente. Da especificação de linguagem : "A transferência de controle nunca tem permissão para inserir um manipulador catch ou cláusula finally, exceto por meio do mecanismo de tratamento de exceção." e "A transferência de controle de uma região protegida só é permitida por meio de uma instrução de exceção (leave, end.filter, end.catch ou end.finally)." A brfamília de instruções do ramo não pode fazer isso.
Não assinado de

1
@Não assinado: também é uma boa limitação, permitindo que seria bizarro :)
user7116

Respostas:


150

finallyos blocos são executados quer uma exceção seja lançada ou não Se uma exceção for lançada, o que diabos faria continue? Você não pode continuar a execução do loop, porque uma exceção não capturada transferirá o controle para outra função.

Mesmo que nenhuma exceção seja lançada, finallyserá executado quando outras instruções de transferência de controle dentro do bloco try / catch forem executadas, como um return, por exemplo, que traz o mesmo problema.

Em suma, com a semântica finallydisso, não faz sentido permitir a transferência de controle de dentro de um finallybloco para fora dele.

Apoiar isso com alguma semântica alternativa seria mais confuso do que útil, uma vez que existem soluções alternativas simples que tornam o comportamento pretendido muito mais claro. Assim, você obtém um erro e é forçado a pensar corretamente sobre o problema. É a ideia geral de "jogar você no poço do sucesso" que existe no C #.

C #, você e o out if sucesso

Se você deseja ignorar as exceções (na maioria das vezes é uma má ideia) e continuar executando o loop, use um bloco catch all:

foreach ( var in list )
{
    try{
        //some code
    }catch{
        continue;
    }
}

Se você quiser continueapenas quando nenhuma exceção não detectada for lançada, basta colocar continuefora do bloco try.


12
Aceitarei isso como resposta, porque você me fez entender os motivos pelos quais a Microsoft possivelmente decidiu não aceitar continuar em um finalmente. Provavelmente essas imagens também me convenceram :)
lpaloub

20
você pode elaborar a ideia de "jogá-lo no poço do sucesso"? Não entendi :-D
Formiga


13
A imagem foi feita por Jon Skeet, btw. Consegui aqui: msmvps.com/blogs/jon_skeet/archive/2010/09/02/…
R. Martinho Fernandes

1
Claro, um continueem um finallybloco estará OK se ele apenas continuar um loop local definido dentro do finallybloco. Na pergunta, no entanto, ele tenta continuar um loop "externo". Instruções semelhantes que você não pode ter dentro de um finallybloco são return, break(ao sair do bloco) e goto(ao ir para um rótulo fora do finallybloco). Para uma discussão Java relacionada, consulte Retornando de um bloco finally em Java .
Jeppe Stig Nielsen

32

Aqui está uma fonte confiável:

Uma instrução continue não pode sair de um bloco finally (Seção 8.10). Quando uma instrução continue ocorre dentro de um bloco finally, o destino da instrução continue deve estar dentro do mesmo bloco finally; caso contrário, ocorrerá um erro em tempo de compilação.

Ele é retirado do MSDN, 8.9.2 A instrução continue .

A documentação diz que:

As instruções de um bloco finally são sempre executadas quando o controle sai de uma instrução try. Isso é verdade se a transferência de controle ocorre como resultado da execução normal, como resultado da execução de uma instrução break, continue, goto ou return, ou como resultado da propagação de uma exceção da instrução try. Se uma exceção for lançada durante a execução de um bloco finally, a exceção será propagada para a próxima instrução try envolvente. Se outra exceção estiver em processo de propagação, essa exceção será perdida. O processo de propagação de uma exceção é discutido posteriormente na descrição da instrução throw (Seção 8.9.5).

É a partir daqui 8.10 A instrução try .


31

Você pode pensar que faz sentido, mas não faz sentido na verdade.

foreach (var v in List)
{
    try
    {
        //Some code
    }
    catch (Exception)
    {
        //Some more code
        break; or return;
    }
    finally
    {
        continue;
    }
}

O que você pretende interromper ou continuar quando uma exceção for lançada? A equipe do compilador C # não quer tomar decisões por conta própria assumindo breakou continue. Em vez disso, eles decidiram reclamar que a situação do desenvolvedor seria ambígua para transferir o controle finally block.

Portanto, é função do desenvolvedor declarar claramente o que ele pretende fazer, em vez de o compilador assumir outra coisa.

Espero que você entenda porque isso não compila!


Em uma nota relacionada, mesmo se não houvesse "captura", o caminho de execução após uma finallyinstrução seria afetado pelo fato de o catchter saído por meio de exceção e não há mecanismo para dizer como isso deve interagir com um continue. Gosto do seu exemplo, porém, porque mostra um problema ainda maior.
supercat

@supercat Concordo com você, minha resposta mostra um exemplo de situação ambígua para o compilador ainda existem tantos problemas com esta abordagem.
Sriram Sakthivel

16

Como outros afirmaram, mas com foco nas exceções, trata-se realmente de um tratamento ambíguo da transferência de controle.

Em sua mente, você provavelmente está pensando em um cenário como este:

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

Teoricamente, você pode rastrear o fluxo de controle e dizer, sim, isso é "ok". Nenhuma exceção é lançada, nenhum controle é transferido. Mas os designers da linguagem C # tinham outros problemas em mente.

A exceção lançada

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

O retorno

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

A separação

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

Portanto, em conclusão, sim, enquanto não é uma ligeira possibilidade de usar uma continueem situações onde o controle não está sendo transferido, mas um bom negócio (maioria?) Dos casos envolvem exceções ou returnblocos. Os designers da linguagem acharam que isso seria muito ambíguo e (provavelmente) impossível de garantir no momento da compilação que o seu continueseja usado apenas nos casos em que o fluxo de controle não está sendo transferido.


Tenho a mesma situação em java quando o proguard estava travando durante a ofuscação. Sua explicação é muito boa. C # fornece erro de tempo de compilação - perfeitamente faz sentido.
Dev_Vikram,

11

Em geral continue, não faz sentido quando usado em finallybloco. Dê uma olhada neste:

foreach (var item in list)
{
    try
    {
        throw new Exception();
    }
    finally{
        //doesn't make sense as we are after exception
        continue;
    }
}

"Em geral" muitas coisas não fazem sentido. E isso não significa que não deva funcionar "em particular". IE: continuenão faz sentido fora da instrução loop, mas não significa que não seja compatível.
zerkms de

Acho que faz algum sentido. itempode ser arquivo, leitura falhou -> finallyfecha o arquivo. continueimpede o resto do processamento.
jnovacho 01 de

5

"Isso não vai compilar e acho que faz todo o sentido"

Bem, acho que não.

Quando você literalmente tem, catch(Exception)então você não precisa do finalmente (e provavelmente nem mesmo do continue).

Quando você tiver o mais realista catch(SomeException), o que deve acontecer quando uma exceção não for detectada? Você continuequer ir para um lado, a exceção tratando de outro.


2
Eu acho que você poderia precisar finallyquando tiver catch. É comumente usado para fechar recursos de maneira confiável.
jnovacho 01 de

Não são apenas exceções. Você poderia facilmente ter uma returndeclaração dentro de seus blocos tryou catch. O que fazemos agora? Retornar ou continuar o loop? (e se continuarmos, e se for o último item a iterar? Então, continuamos fora do foreache o retorno nunca acontece?) EDITAR: Ou mesmo gotopara um rótulo fora do loop, praticamente qualquer ação que transfere o controle para fora do foreachloop .
Chris Sinclair

2
Eu não concordo com "Quando você literalmente tem catch (Exception), então você não precisa do finally (e provavelmente nem mesmo do continue).". E se eu precisar fazer uma operação independentemente da exceção gerada ou não?
lpaloub 01 de

OK, o finalmente fornece returns adicionais dentro do bloco. Mas normalmente a execução continua após o pega-tudo.
Henk Holterman

'finalmente' não é 'pega'. Deve ser usado para código de limpeza. um bloco finally é executado independentemente se uma exceção é lançada ou não . Coisas como fechar arquivos ou liberar memória (se você estiver usando código não gerenciado) etc. essas são as coisas que você coloca em um bloco finally
Robotnik

3

Você não pode deixar o corpo de um bloco final. Isso inclui pausa, retorno e, no seu caso, continuar palavras-chave.


3

O finallybloco pode ser executado com uma exceção aguardando para ser relançado. Realmente não faria sentido poder sair do bloco (por um continueou qualquer outra coisa) sem relançar a exceção.

Se você quiser continuar seu loop, aconteça o que acontecer, você não precisa da instrução finally: apenas capture a exceção e não lance novamente.


1

finallyé executado quer uma exceção não capturada seja lançada ou não. Outros já explicaram por que isso torna continueilógico, mas aqui está uma alternativa que segue o espírito do que este código parece estar pedindo. Basicamente, finally { continue; }está dizendo:

  1. Quando houver exceções detectadas, continue
  2. Quando houver exceções não detectadas, permita que sejam lançadas, mas continue

(1) poderia ser satisfeito colocando continueno final de cada um catch, e (2) poderia ser satisfeito armazenando exceções não capturadas para serem lançadas posteriormente. Você poderia escrever assim:

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

Na verdade, finallytambém teria sido executado no terceiro caso, onde não houve exceções lançadas, capturadas ou não capturadas. Se isso for desejado, você pode, é claro, colocar um único continuebloco após o bloco try-catch em vez de dentro de cada um catch.


1

Tecnicamente falando, é uma limitação do CIL subjacente. Da especificação de idioma :

A transferência de controle nunca tem permissão para inserir um manipulador catch ou cláusula finally, exceto por meio do mecanismo de tratamento de exceção.

e

Transferir o controlo de uma região protegida apenas é permitida através de uma instrução de excepção ( leave, end.filter, end.catch, ou end.finally)

Na página doc para obter as brinstruções :

As transferências de controle para dentro e fora dos blocos try, catch, filter e finalmente não podem ser executadas por esta instrução.

Este último é válido para todas as instruções de desvio, incluindo beq, brfalse, etc.


-1

Os designers da linguagem simplesmente não queriam (ou não podiam) raciocinar sobre a semântica de um bloco final sendo encerrado por uma transferência de controle.

Um problema, ou talvez o principal, é que o finallybloco é executado como parte de alguma transferência de controle não local (processamento de exceção). O alvo dessa transferência de controle não é o loop envolvente; o processamento da exceção aborta o loop e continua a se desenrolar ainda mais.

Se tivermos uma transferência de controle fora do finallybloco de limpeza, a transferência de controle original está sendo "sequestrada". Ele é cancelado e o controle vai para outro lugar.

A semântica pode ser resolvida. Outras línguas têm.

Os projetistas do C # decidiram simplesmente não permitir transferências de controle estáticas do tipo "goto", simplificando um pouco as coisas.

No entanto, mesmo se você fizer isso, não resolverá a questão do que acontece se uma transferência dinâmica for iniciada de a finally: e se o bloco finally chamar uma função e essa função lançar? O processamento da exceção original é então "sequestrado".

Se você resolver a semântica dessa segunda forma de sequestro, não há razão para banir o primeiro tipo. Eles são realmente a mesma coisa: uma transferência de controle é uma transferência de controle, seja do mesmo escopo lexical ou não.


A diferença é que, finallypor definição, deve ser seguido imediatamente pela continuação da transferência que a disparou (uma exceção não capturada return, etc.). Seria fácil permitir transferências do tipo "goto" dentro finally(quais idiomas fazem isso, a propósito?), Mas fazer isso significaria que isso try { return } finally { ... } pode não retornar , o que é completamente inesperado. Se finallychama uma função que lança, não é realmente a mesma coisa, porque já esperamos que exceções possam ocorrer a qualquer momento e interromper o fluxo normal.
nmclean 01 de

@nmclean: Na verdade, uma exceção que escapa finallyé quase a mesma coisa, pois pode resultar em um fluxo de programa inesperado e ilógico. A única diferença é que os designers da linguagem, sendo incapazes de rejeitar todos os programas onde um bloco finalmente poderia lançar uma exceção não tratada, em vez disso, permitem que tais programas sejam compilados e esperam que o programa possa aceitar quaisquer consequências que possam seguir o abandono da exceção anterior. seqüência.
supercat

@supercat Concordo que isso quebra o fluxo esperado, mas meu ponto é que esse é sempre o caso para exceções não tratadas, seja o fluxo atual a finallyou não. Pela lógica do último parágrafo de Kaz, as funções também deveriam ser livres para afetar o fluxo no escopo de seu chamador por meio de continueetc., porque elas podem fazer isso por meio de exceções. Mas é claro que isso não é permitido; as exceções são a única exceção a esta regra, daí o nome "exceção" (ou "interrupção").
nmclean

@nmclean: Exceções não aninhadas representam um fluxo de controle que difere da execução normal, mas ainda é estruturado. A instrução após um bloco só deve ser executada se todas as exceções que ocorreram dentro desse bloco forem detectadas dentro dele. Isso pode parecer uma expectativa razoável, mas uma exceção que ocorre dentro de um finallybloco pode violá-la. Realmente uma situação desagradável, que IMHO deveria ter sido resolvida pelo menos deixando os finallyblocos saberem sobre quaisquer exceções pendentes para que eles possam construir objetos de exceção compostos.
supercat

@mmclean Desculpe, não concordo que o nome "exceção" venha de alguma "exceção à regra" (em relação ao controle) específica para C #, LOL. O aspecto de alteração do fluxo de controle de uma exceção é simplesmente uma transferência de controle dinâmico não local. Uma transferência regular de controle dentro do mesmo escopo lexical (não ocorrendo desenrolamento) pode ser considerada como um caso especial.
Kaz
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.