Por variáveis ??não são declaradas em “tentar” no âmbito de “pegar” ou “finalmente”?

StackOverflow https://stackoverflow.com/questions/94977

Pergunta

Em C # e Java (e possivelmente outras línguas também), as variáveis ??declaradas em um bloco "try" não estão no escopo no correspondente "pegar" ou "finalmente" blocos. 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
}

Neste código, um erro de tempo de compilação ocorre na referência à s no bloco de captura, porque s é somente no escopo no 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 esta questão parece ser a vez declarar variáveis, pouco 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 a me, (1) este se sente como uma solução desajeitado, e (2) que resulta nas variáveis ??que têm um âmbito maior do que o programador pretendido (todo o restante do método, em vez de apenas na contexto do try-catch-finally).

A minha pergunta é, o que eram / são a razão (s) por trás dessa decisão de projeto de linguagem (em Java, em C #, e / ou em quaisquer outras línguas aplicáveis)?

Foi útil?

Solução

Duas coisas:

  1. Geralmente, Java tem apenas 2 níveis de escopo: global e função. Mas, try / catch é uma exceção (sem trocadilhos). Quando uma exceção é lançada eo objeto exceção fica uma variável atribuído a ele, essa variável objeto só está disponível na seção "pegar" e é destruído tão logo os concluída captura.

  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 foi declarada. Por isso, é impossível dizer quais variáveis ??estarão disponíveis para o catch / finally cláusula. Considere o caso a seguir, onde 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);
    }
    

Este é claramente um problema - quando você atingir o manipulador de exceção, s não foram declarados. Dado que as capturas são destinadas a lidar com circunstâncias e Finallys deve executar, sendo seguro e declarando isso um problema em tempo de compilação excepcionais é muito melhor do que em tempo de execução.

Outras dicas

Como você pode ter certeza, que chegou a parte de declaração em seu bloco de captura? E se a instanciação lança a exceção?

Tradicionalmente, em linguagens de estilo C, o que acontece dentro das chaves estadias dentro das chaves. Eu acho que ter o tempo de vida de um estiramento variável através escopos assim seria intuitivo para a maioria dos programadores. Você pode conseguir o que deseja, colocando os try / catch / finally blocos dentro de um outro nível de chaves. por exemplo.

... code ...
{
    string s = "test";
    try
    {
        // more code
    }
    catch(...)
    {
        Console.Out.WriteLine(s);
    }
}

EDIT: Eu acho que toda regra não tem uma exceção. O seguinte é válido C ++:

int f() { return 0; }

void main() 
{
    int y = 0;

    if (int x = f())
    {
        cout << x;
    }
    else
    {
        cout << x;
    }
}

O escopo de x é a condicional, a cláusula de então e a cláusula else.

Toda a gente trouxe o básico - o que acontece em um bloco de estadias em um bloco. Mas no caso da NET, pode ser útil examinar o que o compilador acha que está acontecendo. Tomemos, por exemplo, o seguinte código try / catch (note que o StreamReader é 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();
        }
    }
}

Este irá compilar para algo semelhante ao seguinte na 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? MSIL respeita os blocos - eles são intrinsecamente parte do código subjacente gerado quando você compilar o C #. O escopo não é apenas hard-set no C # especificação, é na CLR e CLS especificação também.

O escopo protege você, mas você ocasionalmente têm de trabalhar em torno dele. Com o tempo, você se acostumar com ele, e ele começa a sentir natural. Como toda a gente disse, o que acontece em um bloco estadias nesse bloco. Você quer compartilhar algo? Você tem que ir para fora os blocos ...

C ++, a qualquer taxa, o âmbito de uma variável automática é limitada pelas chaves que a rodeiam. Por que alguém iria esperar que este seja diferente por desembolsar uma palavra-chave try fora as chaves?

Como ravenspoint apontou, todos esperam variáveis ??para ser local para o bloco são definidos em. Introduz try um bloco e assim faz catch.

Se você quiser variáveis ??locais tanto try e catch, tente encerrando tanto em um bloco:

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

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

A resposta simples é que C ea maioria das línguas que herdaram sua sintaxe são blocos de escopo. Isso significa que se uma variável é definida em um bloco, ou seja, dentro {}, que é o seu alcance.

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

@burkhard tem a questão de saber por respondido corretamente, mas como uma nota que eu queria acrescentar, enquanto o seu exemplo solução recomendada é bom 99,9999 +% do tempo, não é uma boa prática, é muito mais seguro para qualquer cheque de nulo antes de usar algo instanciar dentro do bloco try, ou inicializar a variável para algo em vez de simplesmente declarando-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, de modo que mesmo quando o que você está fazendo no bloco try é mais complexo do que atribuir uma string, você deve ser capaz de acessar com segurança os dados do seu bloco catch.

A resposta, como toda a gente tem a pontas, é muito bonito "que é como blocos são definidos".

Existem algumas propostas para tornar a mais bonita código. Consulte ARM

 try (FileReader in = makeReader(), FileWriter out = makeWriter()) {
       // code using in and out
 } catch(IOException e) {
       // ...
 }

Closures são supostamente para resolver isso também.

with(FileReader in : makeReader()) with(FileWriter out : makeWriter()) {
    // code using in and out
}

UPDATE: ARM é implementado em Java 7. http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html

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

Citar da página 25:

"Note que a declaração StreamReader foi movido fora do bloco Try no exemplo anterior. Isso é necessário porque o último bloco pode não variáveis ??de acesso que são declaradas dentro do bloco Try. Isso faz sentido porque, dependendo onde ocorreu uma exceção, declarações de variáveis ??dentro do bloco Try pode ainda não ter sido executado . "

solução

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

Ele simplesmente tem de trabalhar 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

As variáveis ??são de nível de bloco e restrita a que tentam ou pegar bloco. Similar à definição de uma variável em uma instrução if. Pense desta situação.

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

The String nunca ser declarado, por isso não pode ser dependia.

Uma vez que o bloco try e o bloco catch são 2 blocos diferentes.

No código a seguir, você esperaria s definidas no bloco A ser visível no bloco B?

{ // block A
  string s = "dude";
}

{ // block B
  Console.Out.WriteLine(s); // or printf or whatever
}

No exemplo específico que você deu, inicializar s não pode lançar uma exceção. Então você acha que talvez o seu alcance poderia ser alargado.

Mas, em geral, expressões initialiser pode lançar exceções. Não faria sentido para uma variável cujo initialiser emitiu uma exceção (ou que foi declarado após a outra variável, onde o que aconteceu) para a possibilidade de catch / finally.

Além disso, a legibilidade do código sofreria. A regra em C (e línguas que segui-lo, incluindo C ++, Java e C #) é simples:. Escopos de variável siga blocos

Se você quiser uma variável para estar no escopo de try / catch / finally, mas em nenhum outro lugar, em seguida, enrole a coisa toda em um outro conjunto de chaves (um bloco nu) e declarar a variável antes da tentativa.

Parte da razão pela qual eles não estão no mesmo escopo é porque em qualquer ponto do bloco try, você pode ter jogado a exceção. Se eles estavam no mesmo âmbito, é um desastre em espera, porque dependendo de onde a exceção foi lançada, poderia ser ainda mais ambígua.

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

Quando você declarar uma variável local é colocado na pilha (para alguns tipos de todo o valor do objeto será na pilha, para outros tipos de apenas uma referência será na pilha). Quando há uma exceção dentro de um bloco try, as variáveis ??locais dentro do bloco são libertados, o que significa que a pilha está de volta "desenrolado" ao estado em que estava em no início do bloco try. Isso ocorre por design. É como o try / catch é capaz de desistir de todas as chamadas de função dentro do bloco e coloca o seu sistema de volta em um estado funcional. Sem este mecanismo nunca poderia ter certeza do estado de nada quando ocorre uma exceção.

Tendo código de manipulação de seu erro dependem de variáveis ??declaradas no exterior que têm seus valores alterados dentro do bloco try parece má concepção para mim. O que você está fazendo é essencialmente vazamento recursos intencionalmente a fim de obter informações (neste caso particular, não é tão ruim, porque você só estão vazando informações, mas imagine se fosse algum outro recurso? Você está apenas tornando a vida mais difícil para si mesmo no futuro). Eu sugeriria romper seus blocos try em pedaços menores se você precisar de mais granularidade em tratamento de erros.

Quando você tem um try catch, você deve na maior parte sabe que os erros que ele pode jogar. Theese classes de exceção normaly dizer tudo o que precisa sobre a exceção. Se não, você deve fazer você próprio classes de exceção e passar essa informação adiante. Dessa forma, você nunca vai precisar de ter as variáveis ??de dentro do bloco try, porque a exceção é auto explainatory. Então, se você precisa fazer isso muito, penso em você está design, e tentar pensar se há alguma outra maneira, que você pode prever exceções vindo, ou usar a informação vindo de exceções, e então talvez relançar o seu próprio exceção com mais informações.

Como foi assinalado por outros usuários, as chaves definir o escopo em praticamente todas as linguagem de estilo C, que eu saiba.

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

em C #, se é uma variável complexa, você vai querer implementar IDisposable. Você pode então usar try / catch / finally e chamar obj.Dispose () no bloco finally. Ou você pode usar a palavra-chave usando, que irá chamar automaticamente o Descarte no final da seção de código.

Em Python eles são visíveis no catch / finally blocos se a linha declarando-os não jogar.

E se a exceção é lançada em algum código que está acima da declaração da variável. O que significa, a declaração em si não foi aconteceu neste caso.

try {

       //doSomeWork // Exception is thrown in this line. 
       String s;
       //doRestOfTheWork

} catch (Exception) {
        //Use s;//Problem here
} finally {
        //Use s;//Problem here
}

Enquanto no seu exemplo é estranho que não trabalho, levar um presente semelhante:

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

Isso faria com que a captura de lançar uma exceção de referência nula se o código 1 quebrou. Agora, enquanto a semântica de try / catch são muito bem compreendido, este seria um caso de canto irritante, já que s é definido com um valor inicial, por isso deve, em teoria, nunca será nula, mas sob a semântica compartilhada, que seria.

Mais uma vez isso poderia, em teoria, ser fixado por 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, ele permite que a semântica do escopo a ser definida globalmente sem exceção, especificamente, os moradores durar enquanto o {} eles são definidos no, em todos os casos. ponto menor, mas um ponto.

Finalmente, a fim de fazer o que você quiser, você pode adicionar um conjunto de suportes em torno da try catch. Dá-lhe o alcance que você quer, embora ele vem com o custo 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 alguma coisa no bloco try acionou a exceção seu conteúdo namespace não pode ser confiável -. Ou seja, fazendo referência 's' no bloco catch String pode causar o lançamento de mais uma exceção

Bem, se não lançar um erro de compilação, e você poderia declará-la para o resto do método, então não haveria nenhuma maneira apenas declará-la apenas dentro alcance tentativa. É forçá-lo a ser mais explícito quanto ao local onde a variável é suposto que existe e não fazer suposições.

Se ignorarmos a questão de escopo-bloco por um momento, o compilador teria que trabalhar muito mais difícil em uma situação que não está bem definida. Enquanto isso não é impossível, o erro de escopo também obriga você, o autor do código, para perceber a implicação do código que você escreve (que a string s podem ser nulo no bloco catch). Se o seu código era legal, no caso de uma exceção OutOfMemory, s nem sequer é garantido para ser atribuído um slot de memória:

// 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, compilador) também forçá-lo para inicializar as variáveis ??antes de serem usados. No bloco catch apresentado não pode garantir isso.

Então vamos acabar com o compilador ter que fazer um monte de trabalho, o que na prática não fornecer muito benefício e, provavelmente, as pessoas confundem e levá-los para perguntar por que try / catch funciona de forma diferente.

Além de consistência, não permitindo que nada extravagante e aderindo à semântica de escopo já estabelecidos utilizados em todo o idioma, o compilador e CLR são capazes de fornecer uma garantia maior do estado de uma variável dentro de um bloco catch. Que ele existe e foi inicializado.

Note que os projetistas da linguagem ter feito um bom trabalho com outras construções como usando e Bloqueio , onde o problema e alcance é bem definida, que permite escrever código mais claro .

por exemplo. usando palavra-chave com IDisposable objetos 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 o seu try / catch / finally é difícil de entender, tente refatoração ou introduzir outra camada de engano com uma classe intermediária que encapsula a semântica do que você está tentando realizar. Sem ver o código real, é difícil de ser mais específico.

Em vez de uma variável local, uma propriedade pública pode ser declarada; este também deve evitar outro erro potencial de uma variável não atribuída. public String S {get; conjunto; }

C # Spec (15,2) afirma: "As escopo de uma variável local ou constante declarada em um bloco ist o bloco ".

(em seu primeiro exemplo do bloco try é o bloco onde "S" é declarado)

Se a operação de atribuição falhar a sua instrução catch terá uma volta referência nula para a variável não atribuída.

C # 3.0:

string html = new Func<string>(() =>
{
    string webpage;

    try
    {
        using(WebClient downloader = new WebClient())
        {
            webpage = downloader.DownloadString(url);
        }
    }
    catch(WebException)
    {
        Console.WriteLine("Download failed.");  
    }

    return webpage;
})();
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top