c# - when - try catch divisão por zero java




Por que as variáveis não são declaradas em “try” no escopo em “catch” ou “finally”? (19)

@ Burkhard tem a questão de por que respondeu corretamente, mas como uma nota que eu queria adicionar, enquanto seu exemplo de solução recomendada é bom 99,9999 +% de tempo, não é uma boa prática, é muito mais seguro para verificar nulo antes de usar algo instanciar dentro do bloco try, ou inicializar a variável para algo em vez de apenas declará-lo antes do bloco try. Por exemplo:

string s = String.Empty;
try
{
    //do work
}
catch
{
   //safely access s
   Console.WriteLine(s);
}

Ou:

string s;
try
{
    //do work
}
catch
{
   if (!String.IsNullOrEmpty(s))
   {
       //safely access s
       Console.WriteLine(s);
   }
}

Isso deve fornecer escalabilidade na solução alternativa, de modo que, mesmo quando o que você está fazendo no bloco try seja mais complexo do que atribuir uma string, você poderá acessar com segurança os dados de seu bloco catch.

Em C # e em Java (e possivelmente outras linguagens também), as variáveis ​​declaradas em um bloco "try" não estão no escopo nos blocos "catch" ou "finally" correspondentes. Por exemplo, o código a seguir não compila:

try {
  String s = "test";
  // (more code...)
}
catch {
  Console.Out.WriteLine(s);  //Java fans: think "System.out.println" here instead
}

Nesse código, ocorre um erro em tempo de compilação na referência a s no bloco catch, porque s está apenas no escopo do bloco try. (Em Java, o erro de compilação é "s não pode ser resolvido"; em C #, é "O nome 's' não existe no contexto atual".)

A solução geral para este problema parece ser declarar variáveis ​​antes do bloco try, em vez de dentro do bloco try:

String s;
try {
  s = "test";
  // (more code...)
}
catch {
  Console.Out.WriteLine(s);  //Java fans: think "System.out.println" here instead
}

No entanto, pelo menos para mim, (1) isso parece uma solução desajeitada, e (2) resulta em variáveis ​​que têm um escopo maior do que o programador pretendido (todo o restante do método, em vez de apenas no contexto da try-catch-finally).

A minha pergunta é: quais eram / são a (s) razão (ões) por trás desta decisão de design da linguagem (em Java, em C # e / ou em quaisquer outros idiomas aplicáveis)?


A resposta simples é que C e a maioria das linguagens que herdaram sua sintaxe têm escopo de bloco. Isso significa que se uma variável é definida em um bloco, ou seja, dentro de {}, esse é seu escopo.

A exceção, a propósito, é JavaScript, que tem uma sintaxe semelhante, mas tem escopo de função. Em JavaScript, uma variável declarada em um bloco try está no escopo no bloco catch e em qualquer outro lugar em sua função de contenção.


As variáveis ​​são em nível de bloco e restritas a esse bloco Try ou Catch. Semelhante à definição de uma variável em uma instrução if. Pense nessa situação.

try {    
    fileOpen("no real file Name");    
    String s = "GO TROJANS"; 
} catch (Exception) {   
    print(s); 
}

A String nunca seria declarada, então não pode ser dependida.


Bem, se ele não gerar um erro de compilação e você puder declará-lo pelo resto do método, não haverá como declará-lo apenas no escopo try. Está forçando você a ser explícito sobre onde a variável deve existir e não faz suposições.


Como foi apontado por outros usuários, as chaves definem o escopo em praticamente todas as linguagens de estilo C que eu conheço.

Se é uma variável simples, por que você se importa quanto tempo vai estar no escopo? Não é um grande negócio.

em C #, se for uma variável complexa, você desejará implementar IDisposable. Você pode usar try / catch / finally e chamar obj.Dispose () no bloco finally. Ou você pode usar a palavra-chave using, que chamará automaticamente a seção Dispose no final do código.


Como o ravenspoint apontou, todos esperam que as variáveis ​​sejam locais para o bloco em que estão definidas. try introduz um bloco e o mesmo acontece.

Se você quiser variáveis ​​locais para try e catch , tente catch ambas em um bloco:

// here is some code
{
    string s;
    try
    {

        throw new Exception(":(")
    }
    catch (Exception e)
    {
        Debug.WriteLine(s);
    }
}

De acordo com a seção intitulada "Como lançar e capturar exceções" na lição 2 do Kit de treinamento em ritmo individual MCTS (exame 70-536): Microsoft® .NET Framework 2.0 - Application Development Foundation , a razão é que a exceção pode ter ocorrido antes das declarações de variáveis ​​no bloco try (como outros já notaram).

Citação da página 25:

"Observe que a declaração StreamReader foi movida para fora do bloco Try no exemplo anterior. Isso é necessário porque o bloco Finally não pode acessar variáveis ​​que são declaradas dentro do bloco Try. Isso faz sentido porque dependendo de onde uma exceção ocorreu, declarações de variável dentro do Tente bloquear pode ainda não ter sido executado ".


Duas coisas:

  1. Geralmente, o Java tem apenas dois níveis de escopo: global e função. Mas try / catch é uma exceção (sem trocadilhos). Quando uma exceção é lançada e o objeto de exceção recebe uma variável atribuída a ela, essa variável de objeto só está disponível na seção "catch" e é destruída assim que a captura for concluída.

  2. (e mais importante). Você não pode saber onde no bloco try a exceção foi lançada. Pode ter sido antes de sua variável ser declarada. Portanto, é impossível dizer quais variáveis ​​estarão disponíveis para a cláusula catch / finally. Considere o seguinte caso, em que o escopo é como você sugeriu:

    
    try
    {
        throw new ArgumentException("some operation that throws an exception");
        string s = "blah";
    }
    catch (e as ArgumentException)
    {  
        Console.Out.WriteLine(s);
    }

Isso claramente é um problema - quando você alcança o manipulador de exceções, ele não será declarado. Dado que as capturas são destinadas a lidar com circunstâncias excepcionais e finalmente devem ser executadas, ser seguro e declarar que isso é um problema em tempo de compilação é muito melhor do que em tempo de execução.


Em C ++, de qualquer forma, o escopo de uma variável automática é limitado pelas chaves que a cercam. Por que alguém esperaria que isso fosse diferente colocando uma palavra-chave try fora das chaves?


Em Python, eles são visíveis nos blocos catch / finally se a linha declarando que eles não foram lançados.


Enquanto no seu exemplo é estranho que não funcione, pegue este similar:

    try
    {
         //Code 1
         String s = "1|2";
         //Code 2
    }
    catch
    {
         Console.WriteLine(s.Split('|')[1]);
    }

Isso faria com que a captura lançasse uma exceção de referência nula caso o Código 1 falhasse. Agora, enquanto a semântica de try / catch é bem compreendida, isso seria um caso de canto irritante, já que s é definido com um valor inicial, portanto, em teoria ele nunca deveria ser nulo, mas sob semântica compartilhada, seria.

Novamente, isso poderia, em teoria, ser corrigido, permitindo apenas definições separadas ( String s; s = "1|2"; ), ou algum outro conjunto de condições, mas geralmente é mais fácil simplesmente dizer não.

Além disso, permite que a semântica do escopo seja definida globalmente sem exceção, especificamente, os locais duram desde que {} sejam definidos em todos os casos. Ponto menor, mas um ponto.

Finalmente, para fazer o que você deseja, você pode adicionar um conjunto de colchetes ao redor do try catch. Dá a você o escopo que você quer, embora isso aconteça à custa de um pouco de legibilidade, mas não muito.

{
     String s;
     try
     {
          s = "test";
          //More code
     }
     catch
     {
          Console.WriteLine(s);
     }
}

Meu pensamento seria que, porque algo no bloco try acionou a exceção, seu conteúdo de espaço de nomes não pode ser confiável - ou seja, referenciar o String 's' no bloco catch poderia causar o lançamento de outra exceção.


O C # Spec (15.2) declara "O escopo de uma variável local ou constante declarada em um bloco é o bloco."

(no seu primeiro exemplo, o bloco try é o bloco onde "s" é declarado)


Parte do motivo pelo qual eles não estão no mesmo escopo é porque, em qualquer ponto do bloco try, você pode ter lançado a exceção. Se estivessem no mesmo escopo, é um desastre esperar, porque dependendo de onde a exceção foi lançada, poderia ser ainda mais ambíguo.

Pelo menos quando é declarado fora do bloco try, você sabe com certeza qual poderia ser a variável no mínimo quando uma exceção é lançada; O valor da variável antes do bloco try.


Quando você declara uma variável local, ela é colocada na pilha (para alguns tipos, o valor inteiro do objeto estará na pilha, para outros tipos, apenas uma referência estará na pilha). Quando há uma exceção dentro de um bloco try, as variáveis ​​locais dentro do bloco são liberadas, o que significa que a pilha é "desenrolada" de volta ao estado em que estava no início do bloco try. Isso é por design. É como o try / catch é capaz de retornar de todas as chamadas de função dentro do bloco e coloca seu sistema de volta em um estado funcional. Sem esse mecanismo, você nunca poderia ter certeza do estado de qualquer coisa quando ocorre uma exceção.

Ter seu código de tratamento de erros depende de variáveis ​​declaradas externamente que têm seus valores alterados dentro do bloco try parece um projeto ruim para mim. O que você está fazendo é essencialmente vazar recursos intencionalmente para obter informações (neste caso em particular não é tão ruim porque você está apenas vazando informações, mas imagine se fosse algum outro recurso? Você está apenas tornando a vida mais difícil para si mesmo no futuro). Sugiro que você divida seus blocos try em partes menores se precisar de mais granularidade no tratamento de erros.


Quando você tem um try catch, você deve na maior parte das vezes saber que os erros que ele pode lançar. Estas classes de exceção normalmente dizem tudo o que você precisa sobre a exceção. Caso contrário, você deve criar suas próprias classes de exceção e passar essas informações. Dessa forma, você nunca precisará obter as variáveis ​​de dentro do bloco try, porque a Exceção é auto-explicativa. Então, se você precisa fazer isso muito, pense sobre o seu design, e tente pensar se existe alguma outra maneira, que você pode prever exceções ou usar as informações vindas das exceções, e então talvez recriar sua própria exceção com mais informações.


Se ignorarmos a questão do escopo do bloco por um momento, o compliador teria que trabalhar muito mais em uma situação que não está bem definida. Embora isso não seja impossível, o erro de escopo também força você, o autor do código, a perceber a implicação do código que você escreve (que a string s pode ser nula no bloco catch). Se seu código era legal, no caso de uma exceção OutOfMemory, não é garantido que um código de memória seja alocado:

// won't compile!
try
{
    VeryLargeArray v = new VeryLargeArray(TOO_BIG_CONSTANT); // throws OutOfMemoryException
    string s = "Help";
}
catch
{
    Console.WriteLine(s); // whoops!
}

O CLR (e, portanto, o compilador) também força você a inicializar variáveis ​​antes que elas sejam usadas. No bloco de captura apresentado, não pode garantir isso.

Então, terminamos com o compilador tendo que fazer muito trabalho, o que na prática não oferece muito benefício e provavelmente confundiria as pessoas e as levaria a perguntar por que o try / catch funciona de maneira diferente.

Além da consistência, por não permitir nada sofisticado e aderir à semântica de escopo já estabelecida usada em toda a linguagem, o compilador e o CLR são capazes de fornecer uma garantia maior do estado de uma variável dentro de um bloco catch. Que existe e foi inicializado.

Observe que os designers de linguagem fizeram um bom trabalho com outras construções, como usar e bloquear onde o problema e o escopo estão bem definidos, o que permite escrever código mais claro.

por exemplo, a palavra-chave using com objetos IDisposable em:

using(Writer writer = new Writer())
{
    writer.Write("Hello");
}

é equivalente a:

Writer writer = new Writer();
try
{        
    writer.Write("Hello");
}
finally
{
    if( writer != null)
    {
        ((IDisposable)writer).Dispose();
    }
}

Se seu try / catch / finally é difícil de entender, tente refatorar ou introduzir outra camada de indirecção com uma classe intermediária que encapsula a semântica do que você está tentando realizar. Sem ver código real, é difícil ser mais específico.


Todos os outros trouxeram o básico - o que acontece em um bloco fica em um bloco. Mas no caso do .NET, pode ser útil examinar o que o compilador acha que está acontecendo. Tome, por exemplo, o seguinte código try / catch (observe que o StreamReader está declarado, corretamente, fora dos blocos):

static void TryCatchFinally()
{
    StreamReader sr = null;
    try
    {
        sr = new StreamReader(path);
        Console.WriteLine(sr.ReadToEnd());
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
    finally
    {
        if (sr != null)
        {
            sr.Close();
        }
    }
}

Isso irá compilar para algo semelhante ao seguinte no MSIL:

.method private hidebysig static void  TryCatchFinallyDispose() cil managed
{
  // Code size       53 (0x35)    
  .maxstack  2    
  .locals init ([0] class [mscorlib]System.IO.StreamReader sr,    
           [1] class [mscorlib]System.Exception ex)    
  IL_0000:  ldnull    
  IL_0001:  stloc.0    
  .try    
  {    
    .try    
    {    
      IL_0002:  ldsfld     string UsingTest.Class1::path    
      IL_0007:  newobj     instance void [mscorlib]System.IO.StreamReader::.ctor(string)    
      IL_000c:  stloc.0    
      IL_000d:  ldloc.0    
      IL_000e:  callvirt   instance string [mscorlib]System.IO.TextReader::ReadToEnd()
      IL_0013:  call       void [mscorlib]System.Console::WriteLine(string)    
      IL_0018:  leave.s    IL_0028
    }  // end .try
    catch [mscorlib]System.Exception 
    {
      IL_001a:  stloc.1
      IL_001b:  ldloc.1    
      IL_001c:  callvirt   instance string [mscorlib]System.Exception::ToString()    
      IL_0021:  call       void [mscorlib]System.Console::WriteLine(string)    
      IL_0026:  leave.s    IL_0028    
    }  // end handler    
    IL_0028:  leave.s    IL_0034    
  }  // end .try    
  finally    
  {    
    IL_002a:  ldloc.0    
    IL_002b:  brfalse.s  IL_0033    
    IL_002d:  ldloc.0    
    IL_002e:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()    
    IL_0033:  endfinally    
  }  // end handler    
  IL_0034:  ret    
} // end of method Class1::TryCatchFinallyDispose

O que vemos? O MSIL respeita os blocos - eles são intrinsecamente parte do código subjacente gerado quando você compila seu C #. O escopo não é apenas hard-set na especificação C #, mas também nas especificações CLR e CLS.

O escopo protege você, mas você ocasionalmente precisa contorná-lo. Com o tempo, você se acostuma e começa a parecer natural. Como todo mundo disse, o que acontece em um bloco permanece nesse bloco. Você quer compartilhar alguma coisa? Você tem que sair dos quarteirões ...


Você solução é exatamente o que você deve fazer. Você não pode ter certeza de que sua declaração foi atingida no bloco try, o que resultaria em outra exceção no bloco catch.

Ele simplesmente deve funcionar como escopos separados.

try
    dim i as integer = 10 / 0 ''// Throw an exception
    dim s as string = "hi"
catch (e)
    console.writeln(s) ''// Would throw another exception, if this was allowed to compile
end try




language-design