Em C # e em Java (e possivelmente em outras linguagens também), as variáveis declaradas em um bloco "try" não estão no escopo nos blocos "catch" ou "finalmente" correspondentes. Por exemplo, o código a seguir não é compilado:
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 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 esse problema parece ser 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 para mim, (1) isso parece uma solução complicada e (2) resulta em variáveis com um escopo maior do que o programador pretendia (todo o restante do método, em vez de apenas no contexto do tente pegar finalmente).
Minha pergunta é: quais foram / são as razões por trás dessa decisão de design de linguagem (em Java, em C # e / ou em qualquer outra linguagem aplicável)?
Tradicionalmente, nas linguagens de estilo C, o que acontece dentro do aparelho permanece dentro dele. Eu acho que ter uma vida útil variável variando entre escopos assim não seria intuitivo para a maioria dos programadores. Você pode conseguir o que deseja colocando os blocos try / catch / finalmente dentro de outro nível de chaves. por exemplo
EDIT: Eu acho que todas as regras não têm uma exceção. O seguinte é C ++ válido:
O escopo de x é a condicional, a cláusula then e a cláusula else.
fonte
Todos os outros abordaram o básico - o que acontece em um bloco permanece em um bloco. Mas no caso do .NET, pode ser útil examinar o que o compilador pensa que está acontecendo. Pegue, por exemplo, o seguinte código try / catch (observe que o StreamReader está declarado, corretamente, fora dos blocos):
Isso será compilado para algo semelhante ao seguinte no MSIL:
O que vemos? O MSIL respeita os blocos - eles fazem parte intrinsecamente do código subjacente gerado quando você compila seu C #. O escopo não é apenas rígido na especificação C #, mas também na especificação 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 algo? Você tem que sair dos quarteirões ...
fonte
De qualquer forma, em C ++, o escopo de uma variável automática é limitado pelas chaves que a cercam. Por que alguém esperaria que isso fosse diferente inserindo uma palavra-chave try fora do aparelho?
fonte
Como ravenspoint apontou, todo mundo espera que as variáveis sejam locais para o bloco em que são definidas.
try
Introduz um bloco e o mesmo acontececatch
.Se você deseja que as variáveis sejam locais para ambos
try
ecatch
, tente incluir as duas em um bloco:fonte
A resposta simples é que C e a maioria dos idiomas 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, é o 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 que contém.
fonte
O @burkhard tem a pergunta sobre o porquê de ter respondido corretamente, mas como uma observação que eu gostaria de acrescentar, enquanto o seu exemplo de solução recomendado é bom 99,9999 +% do tempo, não é uma boa prática, é muito mais seguro verificar se é nulo antes de usar algo é instanciado dentro do bloco try ou inicializa a variável para algo em vez de apenas declará-la antes do bloco try. Por exemplo:
Ou:
Isso deve fornecer escalabilidade na solução alternativa, para que, mesmo quando o que você está fazendo no bloco try for mais complexo do que atribuir uma sequência, você possa acessar com segurança os dados do seu bloco catch.
fonte
De acordo com a seção intitulada "Como lançar e capturar exceções" na Lição 2 do Kit de treinamento individual do MCTS (Exame 70-536): Microsoft® .NET Framework 2.0 - Application Development Foundation , o motivo é que a exceção pode ter ocorrido antes das declarações de variáveis no bloco try (como outros já observaram).
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 Finalmente não pode acessar variáveis declaradas no bloco Try. Isso faz sentido porque, dependendo de onde ocorreu uma exceção, as declarações de variáveis dentro do bloco O bloco Try pode ainda não ter sido executado . "
fonte
A resposta, como todos apontaram, é basicamente "é assim que os blocos são definidos".
Existem algumas propostas para tornar o código mais bonito. Veja ARM
Os fechamentos devem tratar disso também.
ATUALIZAÇÃO: O ARM é implementado no Java 7. http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html
fonte
Sua solução é exatamente o que você deve fazer. Você não pode ter certeza de que sua declaração foi alcançada no bloco try, o que resultaria em outra exceção no bloco catch.
Simplesmente deve funcionar como escopos separados.
fonte
As variáveis estão no nível do 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.
A String nunca seria declarada, portanto não pode ser dependente.
fonte
Porque o bloco try e o bloco catch são 2 blocos diferentes.
No código a seguir, você esperaria que s definidos no bloco A sejam visíveis no bloco B?
fonte
Enquanto no seu exemplo é estranho que ele não funcione, faça o seguinte:
Isso faria com que a captura lançasse uma exceção de referência nula se o Código 1 fosse quebrado. Agora, embora a semântica de try / catch seja bem compreendida, esse seria um caso irritante, uma vez que s é definido com um valor inicial, portanto, em teoria, nunca deve ser nulo, mas sob a semântica compartilhada, seria.
Novamente, isso poderia ser corrigido em teoria, permitindo apenas definições separadas (
String s; s = "1|2";
) ou algum outro conjunto de condições, mas geralmente é mais fácil dizer não.Além disso, ele permite que a semântica do escopo seja definida globalmente, sem exceção, especificamente, os locais duram enquanto
{}
que sejam definidos em todos os casos. Ponto menor, mas um ponto.Por fim, para fazer o que você deseja, você pode adicionar um conjunto de colchetes ao redor da captura de tentativa. Dá a você o escopo que você deseja, apesar de custar um pouco de legibilidade, mas não muito.
fonte
No exemplo específico que você deu, a inicialização de s não pode gerar uma exceção. Então você pensaria que talvez seu escopo pudesse ser estendido.
Mas, em geral, as expressões do inicializador podem gerar exceções. Não faria sentido que uma variável cujo inicializador lançou uma exceção (ou que foi declarada após outra variável onde isso aconteceu) estivesse no escopo de catch / finalmente.
Além disso, a legibilidade do código seria prejudicada. A regra em C (e linguagens que a seguem, incluindo C ++, Java e C #) é simples: escopos de variáveis seguem blocos.
Se você deseja que uma variável esteja no escopo de try / catch / finalmente, mas em nenhum outro lugar, envolva tudo em outro conjunto de chaves (um bloco simples) e declare a variável antes da tentativa.
fonte
Parte do motivo pelo qual eles não estão no mesmo escopo é que, em qualquer ponto do bloco try, você pode ter lançado a exceção. Se eles estivessem no mesmo escopo, seria um desastre à espera, 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 ao certo qual poderia ser a variável quando uma exceção é lançada; O valor da variável antes do bloco try.
fonte
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 ocorre por design. É assim que o try / catch é capaz de recuperar todas as chamadas de função dentro do bloco e coloca seu sistema novamente em um estado funcional. Sem esse mecanismo, você nunca poderá ter certeza do estado de qualquer coisa quando ocorrer uma exceção.
Ter seu código de manipulação de erros baseado em variáveis declaradas externamente, cujos valores foram alterados dentro do bloco try, parece um projeto ruim para mim. O que você está fazendo é essencialmente vazar recursos intencionalmente para obter informações (nesse caso em particular, não é tão ruim porque você está vazando apenas informações, mas imagine se fosse algum outro recurso? futuro). Sugiro dividir seus blocos de tentativa em blocos menores se você precisar de mais granularidade no tratamento de erros.
fonte
Quando você tenta capturar, na maioria das vezes você deve saber quais erros isso pode gerar. Essas classes de exceção normalmente informam tudo o que você precisa sobre a exceção. Caso contrário, você deve criar suas próprias classes de exceção e transmitir essas informações. Dessa forma, você nunca precisará obter as variáveis de dentro do bloco try, porque a exceção é auto-explicativa. Portanto, se você precisar fazer isso muito, pense no seu design e tente pensar se existe alguma outra maneira, que você pode prever exceções chegando ou usar as informações provenientes das exceções e, talvez, refazer o seu próprio exceção com mais informações.
fonte
Como foi apontado por outros usuários, os chavetas 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 com quanto tempo isso vai ter no escopo? Não é grande coisa.
em C #, se for uma variável complexa, você desejará implementar o IDisposable. Você pode usar try / catch / finalmente e chamar obj.Dispose () no bloco final. Ou você pode usar a palavra-chave using, que automaticamente chamará Dispose no final da seção de código.
fonte
No Python, eles são visíveis nos blocos catch / finalmente, se a linha que os declara não foi lançada.
fonte
E se a exceção for lançada em algum código que esteja acima da declaração da variável. O que significa que a declaração em si não aconteceu neste caso.
fonte
A C # Spec (15.2) declara "O escopo de uma variável ou constante local declarada em um bloco é o bloco".
(no seu primeiro exemplo, o bloco try é o bloco em que "s" é declarado)
fonte
Meu pensamento seria que, porque algo no bloco try desencadeou a exceção, seu conteúdo do espaço para nome não pode ser confiável - ou seja, referenciar as String 's' no bloco catch pode causar o lançamento de mais uma exceção.
fonte
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 de tentativa. Ele está forçando você a ser explícito sobre onde a variável deveria existir e não faz suposições.
fonte
Se ignorarmos a questão do escopo por um momento, o cumpridor terá 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 obriga 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 o seu código for legal, no caso de uma exceção OutOfMemory, s não garante nem que seja alocado um slot de memória:
O CLR (e, portanto, o compilador) também força você a inicializar variáveis antes que elas sejam usadas. No bloco catch apresentado, não é possível garantir isso.
Então, acabamos com o compilador fazendo muito trabalho, o que, na prática, não oferece muitos benefícios e provavelmente confunde as pessoas e as leva a perguntar por que o try / catch funciona de maneira diferente.
Além da consistência, ao não permitir nada sofisticado e aderir à semântica de escopo já estabelecida usada em todo o idioma, o compilador e o CLR são capazes de fornecer uma maior garantia do estado de uma variável dentro de um bloco de captura. 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 que você escreva um código mais claro.
por exemplo, a palavra-chave using com objetos IDisposable em:
é equivalente a:
Se for difícil entender sua tentativa / captura / finalmente, tente refatorar ou introduzir outra camada de indireção com uma classe intermediária que encapsule a semântica do que você está tentando realizar. Sem ver o código real, é difícil ser mais específico.
fonte
Em vez de uma variável local, uma propriedade pública pode ser declarada; isso também deve evitar outro erro em potencial de uma variável não atribuída. sequência pública S {get; conjunto; }
fonte
Se a operação de atribuição falhar, sua instrução catch terá uma referência nula para a variável não atribuída.
fonte
C # 3.0:
fonte