En C # y en Java (y posiblemente también en otros lenguajes), las variables declaradas en un bloque "try" no están dentro del alcance en los bloques "catch" o "finalmente" correspondientes. Por ejemplo, el siguiente código no se compila:
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 en tiempo de compilación en la referencia a s en el bloque catch, porque s solo está dentro del alcance en el bloque try. (En Java, el error de compilación es "s 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 se siente como una solución torpe, y (2) da como resultado que las variables tengan un alcance mayor que el programador previsto (todo el resto del método, en lugar de solo en el contexto del intentar-atrapar-finalmente).
Mi pregunta es, ¿cuáles fueron / son las razones detrás de esta decisión de diseño de lenguaje (en Java, en C # y / o en cualquier otro lenguaje aplicable)?
Tradicionalmente, en lenguajes de estilo C, lo que sucede dentro de las llaves se mantiene dentro de las llaves. Creo que tener la vida útil de un tramo variable en ámbitos como ese no sería intuitivo para la mayoría de los programadores. Puede lograr lo que desea al encerrar los bloques try / catch / finalmente dentro de otro nivel de llaves. p.ej
EDIT: Creo que todas las reglas no tienen una excepción. Lo siguiente es válido C ++:
El alcance de x es el condicional, la cláusula then y la cláusula else.
fuente
Todos los demás han mencionado lo básico: lo que sucede en un bloque permanece en un bloque. Pero en el caso de .NET, puede ser útil examinar lo que el compilador cree que está sucediendo. Tome, por ejemplo, el siguiente código try / catch (tenga en cuenta que StreamReader se declara, correctamente, fuera de los bloques):
Esto compilará algo similar a lo siguiente en MSIL:
Que vemos MSIL respeta los bloques: son intrínsecamente parte del código subyacente generado cuando compila su C #. El alcance no solo está establecido en la especificación C #, sino también en las especificaciones CLR y CLS.
El alcance lo protege, pero ocasionalmente debe evitarlo. Con el tiempo, te acostumbras y comienza a sentirse natural. Como todos los demás dijeron, lo que sucede en un bloque permanece en ese bloque. ¿Quieres compartir algo? Tienes que salir de los bloques ...
fuente
En cualquier caso, en C ++, el alcance de una variable automática está limitado por las llaves que lo rodean. ¿Por qué alguien esperaría que esto fuera diferente al colocar una palabra clave de prueba fuera de las llaves?
fuente
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éncatch
.Si desea variables locales para ambos
try
ycatch
, intente encerrar ambos en un bloque:fuente
La respuesta simple es que C y la mayoría de los lenguajes que han heredado su sintaxis tienen un alcance de bloque. Eso significa que si una variable se define en un bloque, es decir, dentro de {}, ese es su alcance.
La excepción, por cierto, es JavaScript, que tiene una sintaxis similar, pero tiene un alcance de función. En JavaScript, una variable declarada en un bloque try está dentro del alcance en el bloque catch, y en todas partes en su función de contención.
fuente
@burkhard tiene la pregunta de por qué respondió correctamente, pero como una nota que quería agregar, aunque su ejemplo de solución recomendada es bueno 99.9999 +% de tiempo, no es una buena práctica, es mucho más seguro verificar si es nulo antes de usar algo se instancia dentro del bloque try, o inicializa la variable a algo en lugar de simplemente declararlo antes del bloque try. Por ejemplo:
O:
Esto debería proporcionar escalabilidad en la solución alternativa, de modo que incluso cuando lo que está haciendo en el bloque try sea más complejo que asignar una cadena, debería poder acceder de manera segura a los datos desde su bloque catch.
fuente
De acuerdo con la sección titulada "Cómo lanzar y atrapar excepciones" en la Lección 2 del Kit de capacitación a su propio ritmo MCTS (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 otros ya lo han notado).
Cita de la página 25:
"Observe que la declaración StreamReader se movió fuera del bloque Try en el ejemplo anterior. Esto es necesario porque el bloque Finalmente no puede acceder a las variables declaradas dentro del bloque Try. Esto tiene sentido porque dependiendo de dónde ocurrió una excepción, las declaraciones de variables dentro del Es posible que el bloque Try aún no se haya ejecutado ".
fuente
La respuesta, como todos han señalado, es más o menos "así es como se definen los bloques".
Hay algunas propuestas para hacer el código más bonito. Ver BRAZO
Se supone que los cierres también abordarán esto.
ACTUALIZACIÓN: ARM se implementa en Java 7. http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html
fuente
Su solución es exactamente lo que debe hacer. No puede estar seguro de que su declaración se haya alcanzado incluso en el bloque try, lo que resultaría en otra excepción en el bloque catch.
Simplemente debe funcionar como ámbitos separados.
fuente
Las variables son de nivel de bloque y están restringidas a ese bloque Try o Catch. Similar a la definición de una variable en una declaración if. Piensa en esta situación.
La cadena nunca se declararía, por lo que no se puede depender de ella.
fuente
Porque el bloque try y el bloque catch son 2 bloques diferentes.
En el siguiente código, ¿esperaría que los definidos en el bloque A sean visibles en el bloque B?
fuente
Si bien en su ejemplo es extraño que no funcione, tome este similar:
Esto provocaría que la captura arroje una excepción de referencia nula si se rompe el Código 1. Ahora, si bien 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 bajo una semántica compartida, lo sería.
Nuevamente, esto en teoría podría solucionarse permitiendo solo 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 alcance se defina globalmente sin excepción, específicamente, los locales duran tanto como
{}
se definen 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. Le brinda el alcance que desea, aunque tiene el costo de una pequeña legibilidad, pero no demasiado.
fuente
En el ejemplo específico que ha dado, inicializar s no puede generar una excepción. Entonces pensarías que tal vez su alcance podría extenderse.
Pero en general, las expresiones de inicializador pueden arrojar excepciones. No tendría sentido para una variable cuyo inicializador arrojó una excepción (o que fue declarada después de otra variable donde sucedió eso) para estar en el alcance de catch / finalmente.
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é dentro del alcance 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 intento.
fuente
Parte de la razón por la que no están en el mismo alcance es porque en cualquier punto del bloque try, puede haber lanzado la excepción. Si estuvieran en el mismo alcance, sería un desastre esperar, porque dependiendo de dónde se lanzara la excepción, podría ser aún más ambiguo.
Al menos cuando se declara fuera del bloque try, usted sabe con seguridad cuál podría ser la variable como mínimo cuando se lanza una excepción; El valor de la variable antes del bloque try.
fuente
Cuando declara una variable local, se coloca en la pila (para algunos tipos, el valor completo del objeto estará en la pila, para otros tipos, solo una referencia estará en la pila). Cuando hay una excepción dentro de un bloque try, las variables locales dentro del bloque se liberan, lo que significa que la pila se "desenrolla" al estado en que estaba al principio del bloque try. Esto es por diseño. Así es como el try / catch puede retroceder todas las llamadas de función dentro del bloque y vuelve a poner su sistema en un estado funcional. Sin este mecanismo, nunca podría estar seguro del estado de nada cuando se produce una excepción.
Hacer que su código de manejo de errores se base en variables declaradas externamente que tienen sus valores cambiados dentro del bloque try me parece un mal diseño. Lo que está haciendo es esencialmente filtrar recursos intencionalmente para obtener información (en este caso particular, no es tan malo porque solo está filtrando información, pero ¿se imagina si se tratara de algún otro recurso? futuro). Sugeriría dividir sus bloques de prueba en fragmentos más pequeños si necesita más granularidad en el manejo de errores.
fuente
Cuando tiene un intento de captura, en su mayor parte debe saber 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 sus propias clases de excepción y pasar esa información. De esa forma, nunca necesitará obtener las variables desde el interior del bloque try, porque la excepción se explica por sí misma. Entonces, si necesita hacer esto mucho, piense en su diseño e intente pensar si hay alguna otra manera, que puede predecir las próximas excepciones o utilizar la información proveniente de las excepciones, y luego tal vez volver a lanzar la suya. excepción con más información.
fuente
Como han señalado otros usuarios, las llaves definen el alcance en casi todos los lenguajes de estilo C que conozco.
Si es una variable simple, ¿por qué te importa cuánto tiempo estará dentro del alcance? No es gran cosa.
en C #, si es 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 de código.
fuente
En Python son visibles en los bloques catch / finalmente si la línea que los declara no se lanzó.
fuente
¿Qué pasa si la excepción se produce en algún código que está por encima de la declaración de la variable? Lo que significa que la declaración en sí no se realizó en este caso.
fuente
La especificación C # (15.2) establece que "el alcance de una variable local o constante declarada en un bloque es el bloque".
(en su primer ejemplo, el bloque try es el bloque donde se declara "s")
fuente
Mi pensamiento sería que debido a que algo en el bloque try activó la excepción, no se puede confiar en su contenido del espacio de nombres, es decir, hacer referencia a la cadena 's' en el bloque catch podría provocar el lanzamiento de otra excepción.
fuente
Bueno, si no arroja un error de compilación, y podría declararlo para el resto del método, entonces no habría forma de declararlo solo dentro del alcance de prueba. Te obliga a ser explícito sobre dónde se supone que existe la variable y no hace suposiciones.
fuente
Si ignoramos el problema del bloque de alcance por un momento, el cumplidor 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 alcance 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 asigne una ranura de memoria:
El CLR (y, por lo tanto, el compilador) también lo obliga a inicializar las variables antes de que se usen. En el bloque catch presentado no puede garantizar esto.
Así que terminamos con el compilador teniendo que hacer mucho trabajo, lo que en la práctica no proporciona muchos beneficios y probablemente confundiría a las personas y los llevaría a preguntar por qué try / catch funciona de manera diferente.
Además de la coherencia, al no permitir nada elegante y adherirse a la semántica de alcance ya establecida utilizada en todo el lenguaje, el compilador 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 alcance están bien definidos, lo que le permite escribir código más claro.
por ejemplo, la palabra clave using con objetos IDisposable en:
es equivalente a:
Si su intento / captura / finalmente es difícil de entender, intente 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 el código real, es difícil ser más específico.
fuente
En lugar de una variable local, se podría declarar una propiedad pública; Esto también debería evitar otro error potencial de una variable no asignada. cadena pública S {get; conjunto; }
fuente
Si la operación de asignación falla, su instrucción catch tendrá una referencia nula a la variable no asignada.
fuente
C # 3.0:
fuente