¿Por qué no se declaran las variables en “try” en el ámbito en “catch” o “finally”?

En C # y en Java (y posiblemente también en otros idiomas), las variables declaradas en un bloque “try” no están dentro del scope de los bloques “catch” o “finally” correspondientes. Por ejemplo, el siguiente código no se comstack:

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

En este código, se produce un error de tiempo de comstackción en la referencia a s en el bloque catch, porque s está solo en el scope en el bloque try. (En Java, el error de comstackción es “no se puede resolver”; en C #, es “El nombre ‘s’ no existe en el contexto actual”).

La solución general a este problema parece ser declarar variables justo antes del bloque try, en lugar de dentro del bloque try:

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

Sin embargo, al menos para mí, (1) esto parece una solución torpe, y (2) da como resultado que las variables tengan un scope mayor al previsto por el progtwigdor (todo el rest del método, en lugar de solo en el contexto de la try-catch-finally).

Mi pregunta es, ¿cuáles fueron / son los motivos detrás de esta decisión de diseño del lenguaje (en Java, en C # y / o en cualquier otro idioma aplicable)?

Dos cosas:

  1. Generalmente, Java tiene solo 2 niveles de scope: global y función. Pero, try / catch es una excepción (sin juego de palabras). Cuando se lanza una excepción y el objeto de excepción obtiene una variable asignada, esa variable de objeto solo está disponible dentro de la sección “catch” y se destruye tan pronto como se completa la captura.

  2. (y más importante). No puede saber dónde se lanzó la excepción en el bloque try. Puede haber sido antes de que tu variable fuera declarada. Por lo tanto, es imposible decir qué variables estarán disponibles para la cláusula catch / finally. Considere el siguiente caso, donde el scope es el que sugirió:

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

Esto claramente es un problema: cuando llegue al controlador de excepciones, s no se habrá declarado. Dado que las capturas están destinadas a manejar circunstancias excepcionales y finalmente deben ejecutarse, estar seguros y declarar que esto es un problema en tiempo de comstackción es mucho mejor que en tiempo de ejecución.

¿Cómo puedes estar seguro de que has llegado a la parte de statement en tu bloque de captura? ¿Qué pasa si la instanciación arroja la excepción?

Tradicionalmente, en los lenguajes estilo C, lo que sucede dentro de las llaves se queda dentro de las llaves. Creo que tener la vida útil de un scope variable en ámbitos como ese sería poco intuitivo para la mayoría de los progtwigdores. Puede lograr lo que quiere al encerrar los bloques try / catch / finally dentro de otro nivel de llaves. p.ej

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

EDITAR: Supongo que cada regla tiene una excepción. Lo siguiente es C ++ válido:

 int f() { return 0; } void main() { int y = 0; if (int x = f()) { cout << x; } else { cout << x; } } 

El scope de x es el condicional, la cláusula then y la cláusula else.

En C ++, de todos modos, el scope de una variable automática está limitado por las llaves que lo rodean. ¿Por qué alguien esperaría que esto fuera diferente lanzando una palabra clave try fuera de las llaves?

Todos los demás han sacado lo básico: lo que sucede en un bloque se queda en un bloque. Pero en el caso de .NET, puede ser útil examinar lo que el comstackdor cree que está sucediendo. Tomemos, por ejemplo, el siguiente código try / catch (tenga en cuenta que StreamReader está declarado, correctamente, fuera de los bloques):

 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(); } } } 

Esto comstackrá algo similar a lo siguiente en 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 

¿Qué es lo que vemos? MSIL respeta los bloques: son intrínsecamente parte del código subyacente generado al comstackr su C #. El scope no solo está establecido en la especificación C #, también está en las especificaciones CLR y CLS.

El scope lo protege, pero ocasionalmente tiene que evitarlo. Con el tiempo, te acostumbras y comienza a sentirse natural. Como todos los demás dijeron, lo que ocurre en un bloque permanece en ese bloque. ¿Quieres compartir algo? Tienes que salir de los bloques …

La respuesta simple es que C y la mayoría de los lenguajes que han heredado su syntax tienen un ámbito de bloque. Eso significa que si una variable está definida en un bloque, es decir, dentro de {}, ese es su scope.

La excepción, dicho sea de paso, es JavaScript, que tiene una syntax similar, pero tiene función de ámbito. En JavaScript, una variable declarada en un bloque try está dentro del scope del bloque catch, y en cualquier otro lugar de su función contenedora.

Como señaló ravenspoint, todos esperan que las variables sean locales para el bloque en el que están definidas. try introduce un bloque y también catch .

Si desea que las variables locales try y catch , intente encerrar ambas en un bloque:

 // here is some code { string s; try { throw new Exception(":(") } catch (Exception e) { Debug.WriteLine(s); } } 

@burkhard tiene la pregunta de por qué respondió correctamente, pero como una nota que quería agregar, mientras que el ejemplo de solución recomendada es bueno 99.9999 +% de tiempo, no es una buena práctica, es mucho más seguro comprobar si es nulo antes de usar algo instanciar dentro del bloque try, o inicializar la variable a algo en lugar de simplemente declararlo antes del bloque try. Por ejemplo:

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

O:

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

Esto debería proporcionar escalabilidad en la solución, de modo que incluso cuando lo que está haciendo en el bloque try sea más complejo que asignar una cadena, debe poder acceder de forma segura a los datos de su bloque catch.

La respuesta, como todos han señalado, es más o menos “así es como se definen los bloques”.

Hay algunas propuestas para que el código sea más bonito. Ver ARM

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

Se supone que los cierres también deben abordar esto.

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

ACTUALIZACIÓN: ARM se implementa en Java 7. http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html

De acuerdo con la sección titulada “Cómo lanzar y atrapar excepciones” en la Lección 2 del MCTS Paced Training Kit (Examen 70-536): Microsoft® .NET Framework 2.0-Application Development Foundation , la razón es que la excepción puede haber ocurrido antes de las declaraciones de variables en el bloque try (como ya han notado otros).

Cita de la página 25:

“Observe que la statement StreamReader se movió fuera del bloque Try en el ejemplo anterior. Esto es necesario porque el bloque Finally no puede acceder a las variables que se declaran dentro del bloque Try. Esto tiene sentido porque dependiendo de dónde se produjo una excepción, las declaraciones de variables dentro del Puede que el locking de prueba aún no se haya ejecutado “.

Tu solución es exactamente lo que deberías hacer. No puede estar seguro de que su statement llegó siquiera al bloque try, lo que daría lugar a otra excepción en el bloque catch.

Simplemente debe funcionar como ámbitos 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 

Las variables son de bloque y están restringidas a ese bloque Try o Catch. Similar a definir una variable en una statement if. Piensa en esta situación.

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

La Cadena nunca se declararía, por lo que no se puede depender de ella.

Porque el bloque try y el bloque catch son 2 bloques diferentes.

En el siguiente código, ¿esperarías que s definido en el bloque A sea visible en el bloque B?

 { // block A string s = "dude"; } { // block B Console.Out.WriteLine(s); // or printf or whatever } 

En el ejemplo específico que ha dado, la inicialización de s no puede arrojar una excepción. Entonces, pensarías que tal vez su scope podría extenderse.

Pero, en general, las expresiones del inicializador pueden arrojar excepciones. No tendría sentido que una variable cuyo iniciador lanzara una excepción (o que se declarara después de otra variable donde eso sucedió) esté en el scope de catch / finally.

Además, la legibilidad del código sufriría. La regla en C (y los lenguajes que la siguen, incluidos C ++, Java y C #) es simple: los ámbitos variables siguen a los bloques.

Si desea que una variable esté en el scope de try / catch / finally pero en ningún otro lugar, envuelva todo en otro conjunto de llaves (un bloque desnudo) y declare la variable antes del bash.

Parte de la razón por la que no están en el mismo ámbito es porque en cualquier punto del bloque de prueba, puede haber arrojado la excepción. Si estuvieran en el mismo ámbito, es un desastre en espera, porque dependiendo de dónde se lanzó la excepción, podría ser aún más ambiguo.

Al menos cuando se declara fuera del bloque try, se sabe con certeza cuál será la variable como mínimo cuando se lanza una excepción; El valor de la variable antes del bloque try.

Cuando declaras una variable local, se coloca en la stack (para algunos tipos, el valor completo del objeto estará en la stack, para otros tipos, solo una referencia estará en la stack). Cuando hay una excepción dentro de un bloque try, las variables locales dentro del bloque se liberan, lo que significa que la stack se “desenrolla” de nuevo al estado en el que estaba al comienzo del bloque try. Esto es por diseño. Es la forma en que el try / catch puede salir de todas las llamadas de función dentro del bloque y pone su sistema nuevamente en un estado funcional. Sin este mecanismo, nunca podría estar seguro del estado de nada cuando se produce una excepción.

Tener el código de manejo de errores basado en variables declaradas externamente que tienen sus valores modificados dentro del bloque try me parece un mal diseño. Lo que estás haciendo es esencialmente filtrar recursos intencionalmente para obtener información (en este caso particular, no es tan malo porque solo estás filtrando información, pero imagina si fuera algún otro recurso? Simplemente te estás haciendo la vida más difícil en el futuro). Sugeriría dividir tus bloques de prueba en trozos más pequeños si requieres más granularidad en el manejo de errores.

Cuando tienes una captura de prueba, deberías saber en su mayor parte los errores que podría arrojar. Estas clases de Excepción normalmente dicen todo lo que necesita sobre la excepción. Si no, debe hacer que sea su propia clase de excepción y pasar esa información. De esta forma, nunca necesitará obtener las variables desde el bloque try, porque la excepción es auto explicativa. Por lo tanto, si necesita hacer esto mucho, piense en su diseño y trate de pensar si existe alguna otra forma, puede predecir las excepciones que se reciben o usar la información que proviene de las excepciones y, luego, quizás replantear su propio excepción con más información.

Como han señalado otros usuarios, las llaves definen el scope en casi todos los estilos de lenguaje C que conozco.

Si es una variable simple, ¿por qué te importa cuánto tiempo estará dentro del scope? No es un gran problema.

en C #, si se trata de una variable compleja, querrá implementar IDisposable. Luego puede usar try / catch / finally y llamar a obj.Dispose () en el bloque finally. O puede usar la palabra clave using, que llamará automáticamente a Dispose al final de la sección del código.

En Python son visibles en los bloques catch / finally si la línea que los declara no tira.

¿Qué pasa si la excepción se arroja en algún código que está por encima de la statement de la variable? Lo que significa que la statement en sí no fue realizada en este caso.

 try { //doSomeWork // Exception is thrown in this line. String s; //doRestOfTheWork } catch (Exception) { //Use s;//Problem here } finally { //Use s;//Problem here } 

Si bien en su ejemplo es extraño que no funcione, tome este similar:

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

Esto provocaría que la captura arrojara una excepción de referencia nula si se rompiera el Código 1. Ahora bien, aunque la semántica de try / catch se entiende bastante bien, este sería un caso de esquina molesto, ya que s se define con un valor inicial, por lo que en teoría nunca debería ser nulo, pero sí bajo semántica compartida.

Una vez más, esto podría en teoría corregirse solo permitiendo definiciones separadas ( String s; s = "1|2"; ), o algún otro conjunto de condiciones, pero en general es más fácil decir simplemente no.

Además, permite que la semántica del scope se defina globalmente sin excepción, específicamente, los locales duran tanto como {} se definen en, en todos los casos. Punto menor, pero un punto.

Finalmente, para hacer lo que quiera, puede agregar un conjunto de corchetes alrededor de la captura de prueba. Te da el scope que deseas, aunque sí a costa de un poco de legibilidad, pero no demasiado.

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

Mi pensamiento sería que, debido a que algo en el bloque try desencadenó la excepción, no se puede confiar en su contenido del espacio de nombres, es decir, al hacer referencia a los String ‘s’ en el bloque catch podría provocar el lanzamiento de otra excepción más.

Bueno, si no arroja un error de comstackción, y podría declararlo para el rest del método, entonces no habría forma de declararlo solo dentro del scope de prueba. Te obliga a ser explícito en cuanto a dónde se supone que existe la variable y no hace suposiciones.

Si la operación de asignación falla, su instrucción catch tendrá una referencia nula a la variable no asignada.

La especificación de C # (15.2) establece “El scope de una variable local o constante declarada en un bloque es el bloque”.

(en tu primer ejemplo, el bloque try es el bloque donde se declara “s”)

Saludos, Tamberg

Si ignoramos el problema del bloque de scope por un momento, el comstackdor tendría que trabajar mucho más en una situación que no está bien definida. Si bien esto no es imposible, el error de scope también lo obliga a usted, el autor del código, a darse cuenta de la implicación del código que escribe (que la cadena s puede ser nula en el bloque catch). Si su código era legal, en el caso de una excepción OutOfMemory, ni siquiera se garantiza que se le asigne una ranura de memoria:

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

El CLR (y, por lo tanto, el comstackdor) también te obliga a inicializar las variables antes de que se utilicen. En el bloque de captura presentado no puede garantizar esto.

Así que terminamos con el comstackdor teniendo que hacer un montón de trabajo, que en la práctica no proporciona muchos beneficios y probablemente confundiría a las personas y los llevaría a preguntarse por qué try / catch funciona de manera diferente.

Además de la coherencia, al no permitir nada sofisticado y adherirse a la semántica de scope ya establecida que se utiliza en todo el lenguaje, el comstackdor y CLR pueden proporcionar una mayor garantía del estado de una variable dentro de un bloque catch. Que existe y se ha inicializado.

Tenga en cuenta que los diseñadores de idiomas han hecho un buen trabajo con otras construcciones, como usar y bloquear, donde el problema y el scope están bien definidos, lo que le permite escribir un código más claro.

por ejemplo, la palabra clave using con objetos IDisposable en:

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

es equivalente a:

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

Si su try / catch / finally es difícil de entender, trate de refactorizar o introducir otra capa de indirección con una clase intermedia que encapsule la semántica de lo que está tratando de lograr. Sin ver código real, es difícil ser más específico.

C # 3.0:

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

En lugar de una variable local, se podría declarar una propiedad pública; esto también debería evitar otro posible error de una variable no asignada. cadena pública S {get; conjunto; }