Código inacessível, mas acessível com uma exceção

108

Este código é parte de um aplicativo que lê e grava em um banco de dados ODBC conectado. Ele cria um registro no banco de dados e, em seguida, verifica se um registro foi criado com sucesso e retorna true.

Meu entendimento do fluxo de controle é o seguinte:

command.ExecuteNonQuery()está documentado para lançar um Invalid​Operation​Exceptionquando "uma chamada de método é inválida para o estado atual do objeto". Portanto, se isso acontecer, a execução do trybloco será interrompida, o finallybloco será executado e, a seguir, será executado return false;na parte inferior.

No entanto, meu IDE afirma que o return false;código é inacessível. E parece ser verdade, posso removê-lo e compilá-lo sem reclamações. No entanto, para mim, parece que não haveria valor de retorno para o caminho do código onde a exceção mencionada é lançada.

private static bool createRecord(String table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1;
    } finally {
        command.Dispose();
    }

    return false;
}

Qual é o meu erro de compreensão aqui?

0xCAFEBABE
fonte
41
Nota lateral: não chame Disposeexplicitamente, mas coloque using:using (var command = ...) {command.CommandText = sb.ToString(); return command.ExecuteNonQuery(); }
Dmitry Bychenko
7
Um finallybloqueio significa outra coisa do que você pensa.
Thorbjørn Ravn Andersen

Respostas:

149

Aviso do compilador (nível 2) CS0162

Código inacessível detectado

O compilador detectou um código que nunca será executado.

O que significa apenas dizer que o compilador entende o suficiente por meio da análise estática que não pode ser alcançado e o omite completamente do IL compilado (daí o seu aviso)

Nota : Você pode provar esse fato para si mesmo, tentando acessar o código inacessível com o depurador ou usando um IL Explorer

O finallypode ser executado em uma exceção , (embora isso à parte) não muda o fato (neste caso) de que ainda será uma exceção não detectada . Portanto, o último returnnunca será atingido de qualquer maneira.

  • Se você quiser que o código continue até o último return, sua única opção é Catch the Exception ;

  • Se não, apenas deixe do jeito que está e remova o return.

Exemplo

try 
{
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    return returnValue == 1;
}
catch(<some exception>)
{
   // do something
}
finally 
{
    command.Dispose();
}

return false;

Para citar a documentação

try-finally (referência C #)

Usando um bloco finally, você pode limpar todos os recursos alocados em um bloco try e pode executar o código mesmo se ocorrer uma exceção no bloco try. Normalmente, as instruções de um bloco finally são executadas quando o controle sai de uma instrução try. A transferência de controle pode ocorrer como resultado da execução normal, da execução de uma instrução break, continue, goto ou return, ou da propagação de uma exceção fora da instrução try.

Dentro de uma exceção tratada, o bloco final associado tem garantia de execução. No entanto, se a exceção não for tratada, a execução do bloco finally depende de como a operação de desenrolamento da exceção é acionada. Isso, por sua vez, depende de como seu computador está configurado.

Normalmente, quando uma exceção não tratada termina um aplicativo, não importa se o bloco finally é executado ou não. No entanto, se você tiver instruções em um bloco finally que deve ser executado mesmo nessa situação, uma solução é adicionar um bloco catch à instrução try-finally . Como alternativa, você pode capturar a exceção que pode ser lançada no bloco try de uma instrução try-finally no topo da pilha de chamadas . Ou seja, você pode capturar a exceção no método que chama o método que contém a instrução try-finally, ou no método que chama esse método, ou em qualquer método na pilha de chamadas. Se a exceção não for detectada, a execução do bloco finally depende se o sistema operacional escolhe acionar uma operação de desenrolamento da exceção.

Por último

Ao usar qualquer coisa que suporte a IDisposableinterface (que é projetada para liberar recursos não gerenciados), você pode envolvê-la em uma usinginstrução. O compilador irá gerar um try {} finally {}e chamar internamente Dispose()no objeto

Michael Randall
fonte
1
O que você quer dizer com IL nas primeiras frases?
Clockwork
2
@Clockwork IL é um produto de compilação de código escrito em linguagens .NET de alto nível. Depois de compilar seu código escrito em uma dessas linguagens, você obterá um binário feito de IL. Observe que a linguagem intermediária às vezes também é chamada de linguagem intermediária comum (CIL) ou linguagem intermediária da Microsoft (MSIL).,
Michael Randall
1
Resumindo, porque ele não entendeu as possibilidades são: Ou a tentativa é executada até atingir return e, portanto, ignora o retorno abaixo finalmente OU uma exceção é lançada e esse retorno nunca é alcançado porque a função será encerrada devido a uma exceção sendo jogado.
Felype
86

o bloco finalmente seria executado e, em seguida, executaria o retorno falso; no fundo.

Errado. finallynão engole a exceção. Ele o honra e a exceção será lançada normalmente. Ele apenas executará o código no final antes do final do bloco (com ou sem exceção).

Se você quiser que a exceção seja engolida, você deve usar um catchbloco sem nenhum throw.

Patrick Hofman
fonte
1
o sinppet acima compilará em caso de exceção, o que será retornado?
Ehsan Sajjad
3
Ele compila, mas nunca return falseacertará, pois lançará uma exceção em vez disso @EhsanSajjad
Patrick Hofman
1
parece estranho, compila porque ou irá retornar um valor para bool no caso de não haver exceção e no caso de exceção nada será, então legítimo para satisfazer o tipo de retorno do método?
Ehsan Sajjad
2
O compilador irá apenas ignorar a linha, é para isso que serve o aviso. Então, por que isso é estranho? @EhsanSajjad
Patrick Hofman
3
Curiosidade: na verdade, não é garantido que um bloco finally seja executado se a exceção não for detectada no programa. A especificação não garante isso e os CLRs anteriores NÃO executaram o bloco finally. Acho que a partir do 4.0 (pode ter sido antes) esse comportamento mudou, mas outros tempos de execução ainda podem se comportar de maneira diferente. É um comportamento bastante surpreendente.
Voo
27

O aviso é porque você não usou catche seu método é basicamente escrito assim:

bool SomeMethod()
{
    return true;
    return false; // CS0162 Unreachable code detected
}

Já que você usa finallyapenas para descartar, a solução preferida é utilizar o usingpadrão:

using(var command = new WhateverCommand())
{
     ...
}

Isso é o suficiente, para garantir o que Disposeserá chamado. É garantido que será chamado após a execução bem-sucedida do bloco de código ou após (antes) de algum ponto catch baixo na pilha de chamadas (as chamadas dos pais estão inativas, certo?).

Se não fosse sobre o descarte, então

try { ...; return true; } // only one return
finally { ... }

é o suficiente, já que você nunca terá que retornar falseno final do método (não há necessidade dessa linha). Seu método retorna o resultado da execução do comando ( trueou false) ou lançará uma exceção caso contrário .


Considere também lançar suas próprias exceções envolvendo as exceções esperadas (verifique o construtor InvalidOperationException ):

try { ... }
catch(SomeExpectedException e)
{
    throw new SomeBetterExceptionWithExplanaition("...", e);
}

Isso normalmente é usado para dizer algo mais significativo (útil) para o chamador do que uma exceção de chamada aninhada diria.


Na maioria das vezes, você realmente não se preocupa com exceções não tratadas. Às vezes, você precisa garantir que finallyseja chamado mesmo se a exceção não for tratada. Nesse caso, você simplesmente o pega e joga novamente (veja esta resposta ):

try { ... }
catch { ...; throw; } // re-throw
finally { ... }
Sinatr
fonte
14

Parece que você está procurando por algo assim:

private static bool createRecord(string table,
                                 IDictionary<String,String> data,
                                 System.Data.IDbConnection conn,
                                 OdbcTransaction trans) {
  [... some other code ...]

  // Using: do not call Dispose() explicitly, but wrap IDisposable into using
  using (var command = ...) {
    try {
      // Normal flow:
      command.CommandText = sb.ToString();

      // True if and only if exactly one record affected
      return command.ExecuteNonQuery() == 1;
    }
    catch (DbException) {
      // Exceptional flow (all database exceptions)
      return false;
    }
  }
}

Por favor, note que finally isso não engole nenhuma exceção

finally {
  // This code will be executed; the exception will be efficently re-thrown
}

// And this code will never be reached
Dmitry Bychenko
fonte
8

Você não tem um catchbloco, então a exceção ainda é lançada, o que bloqueia o retorno.

o bloco finalmente seria executado e, em seguida, executaria o retorno falso; no fundo.

Isso está errado, porque o bloco finally seria executado e, então, haveria uma exceção não detectada.

finallyblocos são usados ​​para limpeza e não detectam a exceção. A exceção é lançada antes do retorno, portanto, o retorno nunca será alcançado, porque uma exceção é lançada antes.

Seu IDE está correto, dizendo que nunca será alcançado, porque a exceção será lançada. Apenas catchblocos são capazes de capturar exceções.

Lendo a documentação ,

Normalmente, quando uma exceção não tratada termina um aplicativo, não importa se o bloco finally é executado ou não. No entanto, se você tiver instruções em um bloco finally que deve ser executado mesmo nessa situação, uma solução é adicionar um bloco catch à instrução try-finally . Como alternativa, você pode capturar a exceção que pode ser lançada no bloco try de uma instrução try-finally no topo da pilha de chamadas. Ou seja, você pode capturar a exceção no método que chama o método que contém a instrução try-finally, ou no método que chama esse método, ou em qualquer método na pilha de chamadas. Se a exceção não for detectada, a execução do bloco finally depende se o sistema operacional escolhe acionar uma operação de desenrolamento da exceção .

Isso mostra claramente que o finally não tem a intenção de capturar a exceção e você estaria correto se houvesse uma catchinstrução vazia antes da finallyinstrução.

Ray Wu
fonte
7

Quando a exceção é lançada, a pilha será desfeita (a execução será movida para fora da função) sem retornar um valor, e qualquer bloco catch nos quadros de pilha acima da função capturará a exceção.

Portanto, return falsenunca será executado.

Tente lançar uma exceção manualmente para entender o fluxo de controle:

try {
    command.CommandText = sb.ToString();
    returnValue = command.ExecuteNonQuery();

    // Try this.
    throw new Exception("See where this goes.");

    return returnValue == 1;
} finally {
    command.Dispose();
}
Nisarg
fonte
5

No seu código:

private static bool createRecord(String table, IDictionary<String,String> data, System.Data.IDbConnection conn, OdbcTransaction trans) {

    [... some other code ...]

    int returnValue = 0;
    try {
        command.CommandText = sb.ToString();
        returnValue = command.ExecuteNonQuery();

        return returnValue == 1; // You return here in case no exception is thrown
    } finally {
        command.Dispose(); //You don't have a catch so the exception is passed on if thrown
    }

    return false; // This is never executed because there was either one of the above two exit points of the method reached.
}

o bloco finalmente seria executado e, em seguida, executaria o retorno falso; no fundo

Esta é a falha em sua lógica porque o finallybloco não pegará a exceção e nunca alcançará a última instrução de retorno.

meJustAndrew
fonte
4

A última instrução return falseestá inacessível, porque o bloco try está faltando uma catchparte que trataria a exceção, então a exceção é relançada após o finallybloco e a execução nunca atinge a última instrução.

Martin Staufcik
fonte
2

Você tem dois caminhos de retorno em seu código, o segundo dos quais é inacessível por causa do primeiro. A última instrução em seu trybloco return returnValue == 1;fornece seu retorno normal, portanto, você nunca pode chegar return false;ao final do bloco de método.

FWIW, a ordem de execução relacionada ao finallybloco é: a expressão que fornece o valor de retorno no bloco try será avaliada primeiro, depois o bloco finally será executado e, em seguida, o valor da expressão calculada será retornado (dentro do bloco try).

Com relação ao fluxo na exceção ... sem um catch, o finallyserá executado na exceção antes que a exceção seja então relançada do método; não há caminho de "retorno".

C Robinson
fonte