¿Por qué se requieren corchetes para try-catch?

38

En varios idiomas (al menos Java, ¿también piensa C #?), puede hacer cosas como

if( condition )
    singleStatement;

while( condition )
    singleStatement;

for( var; condition; increment )
    singleStatement;

Entonces, cuando solo tengo una declaración, no necesito agregar un nuevo ámbito con { } . ¿Por qué no puedo hacer esto con try-catch?

try
    singleStatement;
catch(Exception e)
    singleStatement;

¿Hay algo especial en try-catch que requiere tener siempre un nuevo alcance o algo así? Y si es así, ¿no podría el compilador arreglar eso?

    
pregunta Svish 03.11.2011 - 14:49

4 respuestas

23

OMI, se incluyen en Java y C # principalmente porque ya existían en C ++. La verdadera pregunta, entonces, es por qué C ++ es así. De acuerdo con El diseño y la evolución de C ++ (§16.3):

  

La palabra clave try es completamente redundante y también lo son los corchetes { } , excepto cuando en realidad se usan varias declaraciones en un bloque de prueba o un controlador. Por ejemplo, hubiera sido trivial permitir:

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

Sin embargo, encontré esto tan difícil de explicar que se introdujo la redundancia para salvar al personal de soporte de usuarios confundidos.

Edit: En cuanto a por qué esto sería confuso, creo que uno solo tiene que ver las afirmaciones incorrectas en la respuesta de @Tom Jeffery (y, especialmente, el número de votos ascendentes que ha recibido) para darse cuenta de que habría un problema. Para el analizador, esto realmente no es diferente de hacer coincidir else s con if s - carece de llaves para forzar a otra agrupación, todas todas las cláusulas catch coincidirán con la más reciente throw . Para aquellas lenguas mal escritas que lo incluyen, las cláusulas finally harían lo mismo. Desde el punto de vista del analizador, esto no es lo suficientemente diferente de la situación actual como para notarlo, en particular, como están las gramáticas ahora, realmente no hay nada para agrupar las cláusulas catch juntas, los corchetes agrupan las declaraciones controladas por el Cláusulas catch , no las cláusulas catch en sí mismas.

Desde el punto de vista de escribir un analizador, la diferencia es casi demasiado pequeña como para notarla. Si empezamos con algo como esto:

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

statement: compound_statement
         | simple_statement
         ;

statements: 
          | statements statement
          ;

compound_statement: '{' statements '}'

catch_arg: '(' argument ')'

Entonces la diferencia sería entre:

try_clause: 'try' statement

y:

try_clause: 'try' compound_statement

Igualmente, para las cláusulas catch:

catch_clause: 'catch' catch_arg statement

vs.

catch_clause: 'catch' catch_arg compound_statement

Sin embargo, la definición de un bloque try / catch completo no tendría que cambiar en absoluto. De cualquier manera sería algo como:

catch_clauses: 
             | catch_clauses catch_clause
             ;

try_block: try_clause catch_clauses [finally_clause]
         ;

[Aquí estoy usando [whatever] para indicar algo opcional, y estoy dejando de lado la sintaxis para un finally_clause , ya que no creo que tenga ninguna relación con la pregunta.]

Incluso si no intentas seguir toda la definición de la gramática similar a Yacc, el punto se puede resumir con bastante facilidad: esa última afirmación (que comienza con try_block ) es aquella en la que coinciden las cláusulas catch con try cláusulas, y sigue siendo exactamente igual si se requieren llaves o no.

Para reiterar / resumir: las llaves agrupan las declaraciones controladas por el catch s, pero no agrupan el mismo catch s. Como tales, esas llaves tienen absolutamente el efecto no al decidir qué catch va con qué try . Para el analizador / compilador, la tarea es igualmente fácil (o difícil) de cualquier manera. A pesar de esto, la respuesta de @Tom (y la cantidad de votos ascendentes recibidos) ofrece una amplia demostración del hecho de que tal cambio podría confundir a los usuarios.

    
respondido por el Jerry Coffin 03.11.2011 - 17:10
19

En una respuesta acerca de por qué los corchetes son requerido para algunas construcciones de una sola declaración pero no para otras , Eric Lippert escribió:

  

Hay una serie de lugares donde C # requiere un bloque reforzado de   declaraciones en lugar de permitir una declaración "desnuda". Ellos son:

     
  • el cuerpo de un método, un constructor, un destructor, un elemento de acceso a la propiedad, un elemento de acceso a un evento o un elemento de acceso al indexador.
  •   
  • el bloque de un intento, captura, finalmente, región marcada, sin marcar o insegura.
  •   
  • el bloque de una declaración lambda o método anónimo
  •   
  • el bloque de una instrucción if o loop si el bloque contiene directamente una declaración de variable local. (Es decir, "while (x! = 10) int y = 123;"   es ilegal; tienes que apoyar la declaración.)
  •   

En cada uno de estos casos, sería posible llegar a un   gramática no ambigua (o heurística para desambiguar un ambiguo   gramática) para la característica en la que es legal una sola declaración sin espacios.   ¿Pero cuál sería el punto? En cada una de esas situaciones estás   esperando ver varias declaraciones; las declaraciones individuales son las raras,   caso poco probable Parece que realmente no vale la pena hacer el   La gramática no es ambigua para estos casos muy poco probables.

En otras palabras, fue más caro para el equipo del compilador implementarlo de lo que estaba justificado, por el beneficio marginal que proporcionaría.

    
respondido por el Robert Harvey 03.11.2011 - 19:18
13

Creo que es para evitar colgar otros problemas de estilo. Lo siguiente sería ambiguo ...

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?

Podría significar esto:

try {
   try {

   } catch(Exception e1) {

   } catch(Exception e2) {

   } 
} finally {

} 

O ...

try {
   try {

   } catch(Exception e1) {

   } 
} catch(Exception e2) {

} finally {

} 
    
respondido por el Tom Jefferys 03.11.2011 - 15:33
1

Creo que la razón principal es que hay poco que puedas hacer en C # que necesite un bloque try / catch que sea solo una línea. (No puedo pensar en ningún momento de la parte superior de mi cabeza). Puede tener un punto válido en términos del bloque catch, por ejemplo, una declaración de una línea para registrar algo, pero en términos de legibilidad, tiene más sentido (al menos para mí) requerir {}.

    
respondido por el Jetti 03.11.2011 - 14:54

Lea otras preguntas en las etiquetas