Por que os suportes são necessários para try-catch?

38

Em vários idiomas (pelo menos o Java, pense também em C #?) você pode fazer coisas como

if( condition )
    singleStatement;

while( condition )
    singleStatement;

for( var; condition; increment )
    singleStatement;

Então, quando eu tenho apenas uma declaração, não preciso adicionar um novo escopo com { } . Por que não posso fazer isso com try-catch?

try
    singleStatement;
catch(Exception e)
    singleStatement;

Existe algo especial sobre try-catch que requer sempre um novo escopo ou algo assim? E se assim for, o compilador não poderia consertar isso?

    
por Svish 03.11.2011 / 14:49
fonte

4 respostas

23

IMO, eles estão incluídos em Java e C # principalmente porque já existiam em C ++. A verdadeira questão, então, é por que o C ++ é assim. De acordo com O Design e Evolução do C ++ (§16.3):

The try keyword is completely redundant and so are the { } brackets except where multiple statements are actually used in a try-block or a handler. For example, it would have been trivial to allow:

int f()
{
    return g() catch(xxii) { // not C++
        error("G() goofed: xxii");
        return 22;
    };
}

However, I found this so difficult to explain that the redundancy was introduced to save support personnel from confused users.

Edit: Por que isso seria confuso, acho que basta olhar para as afirmações incorretas na resposta do @Tom Jeffery (e, especialmente, o número de votos positivos que recebeu) para perceber que haveria um problema. Para o analisador, isso realmente não é diferente de combinar else s com if s - faltando chaves para forçar outro agrupamento, as cláusulas all catch coincidiriam com as mais recentes throw . Para aqueles languags que incluem, finally cláusulas faria o mesmo. Do ponto de vista do analisador, isso dificilmente é diferente da situação atual para notar - em particular, como as gramáticas estão agora, não há realmente nada para agrupar as cláusulas catch juntas - os colchetes agrupam as instruções controladas pelo catch cláusulas, não as cláusulas de captura em si.

Do ponto de vista de escrever um analisador, a diferença é quase pequena demais para ser notada. Se começarmos com algo assim:

simple_statement: /* won't try to cover all of this */
                ;

statement: compound_statement
         | simple_statement
         ;

statements: 
          | statements statement
          ;

compound_statement: '{' statements '}'

catch_arg: '(' argument ')'

Então a diferença seria entre:

try_clause: 'try' statement

e:

try_clause: 'try' compound_statement

Da mesma forma, para cláusulas catch:

catch_clause: 'catch' catch_arg statement

vs.

catch_clause: 'catch' catch_arg compound_statement

No entanto, a definição de um bloco try / catch completo não precisaria ser alterada. De qualquer forma, seria algo como:

catch_clauses: 
             | catch_clauses catch_clause
             ;

try_block: try_clause catch_clauses [finally_clause]
         ;

[Aqui estou usando [whatever] para indicar algo opcional e estou deixando de fora a sintaxe de finally_clause , já que não acho que tenha qualquer influência sobre a questão.]

Mesmo que você não tente seguir toda a definição gramatical do Yacc, o ponto pode ser resumido com bastante facilidade: a última instrução (começando com try_block ) é aquela em que as cláusulas catch são combinadas com try cláusulas - e permanece exatamente o mesmo se as chaves são necessárias ou não.

Para reiterar / resumir: as chaves agrupam as instruções controladas por o catch s, mas não não agrupam o catch s. Como tal, essas chaves não têm absolutamente nenhum efeito sobre a decisão de qual catch vai com qual try . Para o analisador / compilador, a tarefa é igualmente fácil (ou difícil) de qualquer forma. Apesar disso, a resposta do @ Tom (e o número de votos positivos recebidos) fornece uma ampla demonstração do fato de que essa mudança quase certamente confundiria os usuários.

    
por 03.11.2011 / 17:10
fonte
19

Em uma resposta sobre por que os colchetes são necessário para algumas construções de instrução única, mas não para outras , Eric Lippert escreveu:

There are a number of places where C# requires a braced block of statements rather than allowing a "naked" statement. They are:

  • the body of a method, constructor, destructor, property accessor, event accessor or indexer accessor.
  • the block of a try, catch, finally, checked, unchecked or unsafe region.
  • the block of a statement lambda or anonymous method
  • the block of an if or loop statement if the block directly contains a local variable declaration. (That is, "while (x != 10) int y = 123;" is illegal; you've got to brace the declaration.)

In each of these cases it would be possible to come up with an unambiguous grammar (or heuristics to disambiguate an ambiguous grammar) for the feature where a single unbraced statement is legal. But what would the point be? In each of those situations you are expecting to see multiple statements; single statements are the rare, unlikely case. It seems like it is not realy worth it to make the grammar unambiguous for these very unlikely cases.

Em outras palavras, era mais caro para a equipe de compiladores implementá-lo do que o justificado, para o benefício marginal que ele proporcionaria.

    
por 03.11.2011 / 19:18
fonte
13

Eu acho que é para evitar pendurar outros problemas de estilo. O seguinte seria ambíguo ...

try
    // Do stuff
try
    // Do  more stuff
catch(MyException1 e1)
    // Handle fist exception
catch(MyException2 e2)
    // So which try does this catch belong to?
finally
    // and who does this finally block belong to?

Isso pode significar isso:

try {
   try {

   } catch(Exception e1) {

   } catch(Exception e2) {

   } 
} finally {

} 

Ou ...

try {
   try {

   } catch(Exception e1) {

   } 
} catch(Exception e2) {

} finally {

} 
    
por 03.11.2011 / 15:33
fonte
1

Eu acho que o principal motivo é que há muito pouco que você pode fazer em C # que precisaria de um bloco try / catch que é apenas uma linha. (Eu não consigo pensar em nenhum agora do topo da minha cabeça). Você pode ter um ponto válido em termos do bloco catch, por exemplo, uma declaração de uma linha para registrar algo, mas em termos de legibilidade, faz mais sentido (pelo menos para mim) exigir {}.

    
por 03.11.2011 / 14:54
fonte