c# - reales - ¿Por qué no se declaran las variables en "try" en el ámbito en "catch" o "finally"?
libro de android studio en español pdf (28)
En C # y en Java (y posiblemente también en otros idiomas), las variables declaradas en un bloque "try" no están dentro del alcance de los bloques "catch" o "finally" 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 de tiempo de compilación en la referencia a s en el bloque catch, porque s está solo en el alcance en el bloque try. (En Java, el error de compilació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 alcance mayor al previsto por el programador (todo el resto 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)?
¿Cómo puedes estar seguro de que has llegado a la parte de declaración en tu bloque de captura? ¿Qué pasa si la instanciación arroja la excepción?
¿Qué pasa si la excepción se arroja 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 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
}
@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.
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 en cuanto a dónde se supone que existe la variable y no hace suposiciones.
C # 3.0:
string html = new Func<string>(() =>
{
string webpage;
try
{
using(WebClient downloader = new WebClient())
{
webpage = downloader.DownloadString(url);
}
}
catch(WebException)
{
Console.WriteLine("Download failed.");
}
return webpage;
})();
Como han señalado otros usuarios, las llaves definen el alcance 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 alcance? 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.
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);
}
}
Cuando declaras 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" 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.
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 declaración 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 bloqueo de prueba aún no se haya ejecutado ".
Dos cosas:
Generalmente, Java tiene solo 2 niveles de alcance: 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.
(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 alcance 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 compilación es mucho mejor que en tiempo de ejecución.
En C ++, de todos modos, el alcance 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?
En Python son visibles en los bloques catch / finally si la línea que los declara no tira.
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 alcance 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 alcance 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 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.
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; }
La especificación de C # (15.2) establece "El alcance 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
La respuesta simple es que C y la mayoría de los lenguajes que han heredado su sintaxis tienen un ámbito de bloque. Eso significa que si una variable está definida en un bloque, es decir, dentro de {}, ese es su alcance.
La excepción, dicho sea de paso, es JavaScript, que tiene una sintaxis similar, pero tiene función de ámbito. En JavaScript, una variable declarada en un bloque try está dentro del alcance del bloque catch, y en cualquier otro lugar de su función contenedora.
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 Closures 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
Las variables son de bloque y están restringidas a ese bloque Try o Catch. Similar a definir una variable en una declaración 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.
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.
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.
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
}
Si ignoramos el problema del bloque de alcance por un momento, el compilador 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 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 compilador) 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 compilador 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 alcance ya establecida que se utiliza 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 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.
Si la operación de asignación falla, su instrucción catch tendrá una referencia nula a la variable no asignada.
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 compilador 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 compilará 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 compilar su C #. El alcance no solo está establecido en la especificación C #, también está en las especificaciones CLR y CLS.
El alcance 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 ...
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 alcance variable en ámbitos como ese sería poco intuitivo para la mayoría de los programadores. 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 alcance de x es el condicional, la cláusula then y la cláusula else.
Tu solución es exactamente lo que deberías hacer. No puede estar seguro de que su declaración 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
While in your example it is weird that it does not work, take this similar one:
try
{
//Code 1
String s = "1|2";
//Code 2
}
catch
{
Console.WriteLine(s.Split(''|'')[1]);
}
This would cause the catch to throw a null reference exception if Code 1 broke. Now while the semantics of try/catch are pretty well understood, this would be an annoying corner case, since s is defined with an initial value, so it should in theory never be null, but under shared semantics, it would be.
Again this could in theory be fixed by only allowing separated definitions (String s; s = "1|2";), or some other set of conditions, but it is generally easier to just say no.
Additionally, it allows the semantics of scope to be defined globally without exception, specifically, locals last as long as the {} they are defined in, in all cases. Minor point, but a point.
Finally, in order to do what you want, you can add a set of brackets around the try catch. Gives you the scope you want, although it does come at the cost of a little readability, but not too much.
{
String s;
try
{
s = "test";
//More code
}
catch
{
Console.WriteLine(s);
}
}