¿Por qué no se declaran las variables en el alcance de "intentar" en "captura" o "finalmente"?

StackOverflow https://stackoverflow.com/questions/94977

Pregunta

En C# y Java (y posiblemente también en otros lenguajes), 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 en tiempo de compilación en la referencia a s en el bloque catch, porque s solo está dentro del alcance del 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 que el previsto por el programador (el resto completo del método, en lugar de solo en el contexto de la intentar-capturar-finalmente).

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

¿Fue útil?

Solución

Dos cosas:

  1. Generalmente, Java tiene sólo 2 niveles de alcance:global y funcional.Pero try/catch es una excepción (sin juego de palabras).Cuando se lanza una excepción y al objeto de excepción se le asigna una variable, 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 se puede saber en qué parte del bloque try se lanzó la excepción.Puede que haya sido antes de que se declarara su variable.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 usted 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, no se habrán declarado s.Dado que las capturas están destinadas a hacer frente a circunstancias excepcionales y finalmente debe ejecutar, estar seguro y declarar esto como un problema en tiempo de compilación es mucho mejor que en tiempo de ejecución.

Otros consejos

¿Cómo puede estar seguro de haber llegado a la parte de declaración en su bloque catch?¿Qué pasa si la creación de instancias arroja la excepción?

Tradicionalmente, en los lenguajes de estilo C, lo que sucede dentro de las llaves permanece dentro de las llaves.Creo que hacer que la vida útil de una variable se extienda a través de ámbitos como ese no sería intuitivo para la mayoría de los programadores.Puede lograr lo que desea encerrando 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 hace tener 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.

Todos los demás han mencionado lo básico: lo que sucede en un bloque permanece en el bloque.Pero en el caso de .NET, puede resultar ú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 se compilará en 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é 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 que también está en la especificación CLR y CLS.

El alcance lo protege, pero ocasionalmente tiene que solucionarlo.Con el tiempo, te acostumbras y empieza a parecer natural.Como todos han dicho, lo que pasa en un bloque se queda en ese bloque.¿Quieres compartir algo?Tienes que salir de los bloques...

En C++, en cualquier caso, el alcance de una variable automática está limitado por las llaves que la rodean.¿Por qué alguien esperaría que esto fuera diferente al escribir una palabra clave try fuera de las llaves?

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

Si quieres variables locales para ambos try y catch, intenta encerrar ambos en un bloque:

// here is some code
{
    string s;
    try
    {

        throw new Exception(":(")
    }
    catch (Exception e)
    {
        Debug.WriteLine(s);
    }
}

La respuesta simple es que C y la mayoría de los lenguajes que han heredado su sintaxis tienen 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 funcional.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.

@burkhard tiene la pregunta de por qué se respondió correctamente, pero como nota que quería agregar, si bien el ejemplo de solución recomendado es bueno el 99,9999+% de las veces, no es una buena práctica, es mucho más seguro verificar si hay nulos antes de usar. crear una instancia de algo dentro del bloque try, o inicializar la variable a algo en lugar de simplemente declararla 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, debería poder acceder de forma segura a los datos desde su bloque catch.

La respuesta, como todo el mundo ha señalado, es más o menos "así es como se definen los bloques".

Hay algunas propuestas para embellecer el código.Ver BRAZO

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

Cierres Se supone que debemos abordar esto también.

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

ACTUALIZAR: ARM está implementado en Java 7. http://download.java.net/jdk7/docs/technotes/guides/language/try-with-resources.html

Según la sección titulada "Cómo lanzar y capturar excepciones" en la Lección 2 de Kit de capacitación a su propio ritmo MCTS (examen 70-536):Microsoft® .NET Framework 2.0: base para el desarrollo de aplicaciones, la razón es que la excepción puede haber ocurrido antes de las declaraciones de variables en el bloque try (como otros ya han señalado).

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, es posible que las declaraciones de variables dentro del bloque Try aún no se hayan ejecutado.."

Tu solución es exactamente lo que debes hacer.No puede estar seguro de que su declaración haya sido alcanzada en el bloque try, lo que resultaría en 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 están a nivel 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); 
}

El String nunca se declararía, por lo que no se puede depender de él.

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

En el siguiente código, ¿esperaría que los mensajes definidos en el bloque A fueran visibles 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 generar una excepción.Entonces uno pensaría que tal vez su alcance podría ampliarse.

Pero, en general, las expresiones inicializadoras pueden generar excepciones.No tendría sentido que una variable cuyo inicializador arrojó una excepción (o que fue declarada después de otra variable donde sucedió eso) esté dentro del alcance de catch/finally.

Además, la legibilidad del código se vería afectada.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 entre llaves (un bloque simple) y declare la variable antes del intento.

Parte de la razón por la que no están en el mismo alcance es porque en cualquier punto del bloque try, es posible que se haya producido la excepción.Si estuvieran en el mismo alcance, la espera sería un desastre, porque dependiendo de dónde se lanzó la excepción, podría ser aún más ambigua.

Al menos cuando se declara fuera del bloque try, sabes con seguridad cuál podría ser la variable mínima cuando se lanza una excepción;El valor de la variable antes del bloque try.

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 habrá una referencia 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 el que estaba al comienzo del bloque try.Esto es por diseño.Así es como try/catch puede cancelar todas las llamadas a funciones dentro del bloque y devolver el sistema a un estado funcional.Sin este mecanismo nunca podrías estar seguro del estado de nada cuando ocurre una excepción.

Hacer que su código de manejo de errores dependa de variables declaradas externamente cuyos valores cambian 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 ¿imaginas si fuera algún otro recurso?solo te estás haciendo la vida más difícil en el futuro).Sugeriría dividir los bloques de prueba en partes más pequeñas si necesita más granularidad en el manejo de errores.

Cuando tiene un intento de captura, en su mayor parte debe saber los errores que podría generar.Estas clases de excepción normalmente dicen todo lo que necesita sobre la excepción.De lo contrario, debe crear sus propias clases de excepción y transmitir esa información.De esa manera, nunca necesitarás obtener las variables desde dentro del bloque try, porque la excepción se explica por sí misma.Entonces, si necesita hacer esto con frecuencia, piense en su diseño e intente pensar si hay alguna otra manera de predecir las excepciones que se producirán o utilizar la información proveniente de las excepciones, y luego tal vez volver a generar la suya propia. excepción con más información.

Como han señalado otros usuarios, las llaves definen el alcance en prácticamente todos los lenguajes de estilo C que conozco.

Si es una variable simple, ¿por qué le importa cuánto tiempo estará dentro del alcance?No es gran cosa.

en C#, si es una variable compleja, querrás implementar IDisposable.Luego puede usar try/catch/finally y llamar a obj.Dispose() en el bloque finalmente.O puede usar la palabra clave de uso, 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 se lanzó.

¿Qué pasa si la excepción se produce en algún código que está por encima de la declaración de la variable?Es decir, la declaración en sí no se produjo 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 tu ejemplo es extraño que no funcione, toma este similar:

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

Esto haría que la captura arrojara una excepción de referencia nula si el Código 1 fallara.Ahora bien, si bien la semántica de try/catch se comprende 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 la semántica compartida, lo sería.

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

Además, permite definir la semántica del alcance globalmente sin excepción, específicamente, los locales duran tanto como el {} están definidos en, en todos los casos.Punto menor, pero un punto.

Finalmente, para hacer lo que quieras, puedes agregar un conjunto de corchetes alrededor del try catch.Le brinda el alcance que desea, aunque tiene el costo de un poco de legibilidad, pero no demasiada.

{
     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 el contenido de su espacio de nombres; es decir, hacer referencia a String 's' en el bloque catch podría provocar el lanzamiento de otra excepción más.

Bueno, si no arroja un error de compilación y puedes declararlo para el resto del método, entonces no habría forma de declararlo únicamente dentro del alcance de prueba.Te obliga a ser explícito en cuanto a dónde se supone que existe la variable y no hacer suposiciones.

Si ignoramos la cuestión del bloqueo 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 te obliga a ti, el autor del código, a darte cuenta de las implicaciones del código que escribes (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 a s:

// 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 lo obligan a inicializar las variables antes de usarlas.En el bloque catch presentado no se 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 la gente 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 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 ha sido inicializado.

Tenga en cuenta que los diseñadores del lenguaje han hecho un buen trabajo con otras construcciones como usando y cerrar donde el problema y el alcance están bien definidos, lo que permite escribir código más claro.

p.ej.el usando palabra clave con Desechable objetos 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 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.

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 {obtener;colocar;}

El Especificaciones de C# (15.2) establece "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")

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

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;
})();
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top