2009-01-07 5 views

Respuesta

208

No - en el nivel IL no puede regresar desde dentro de un bloque manejado por excepción. En esencia, lo almacena en una variable y devuelve después

es decir, similar a:

int tmp; 
try { 
    tmp = ... 
} finally { 
    ... 
} 
return tmp; 

por ejemplo (usando reflector):

static int Test() { 
    try { 
     return SomeNumber(); 
    } finally { 
     Foo(); 
    } 
} 

compila a:

.method private hidebysig static int32 Test() cil managed 
{ 
    .maxstack 1 
    .locals init (
     [0] int32 CS$1$0000) 
    L_0000: call int32 Program::SomeNumber() 
    L_0005: stloc.0 
    L_0006: leave.s L_000e 
    L_0008: call void Program::Foo() 
    L_000d: endfinally 
    L_000e: ldloc.0 
    L_000f: ret 
    .try L_0000 to L_0008 finally handler L_0008 to L_000e 
} 

Este básicamente declara una variable local (CS$1$0000), coloca el valor en la variable (i nside the handle block), luego de salir del bloque carga la variable y luego la devuelve. Reflector hace esto como:

private static int Test() 
{ 
    int CS$1$0000; 
    try 
    { 
     CS$1$0000 = SomeNumber(); 
    } 
    finally 
    { 
     Foo(); 
    } 
    return CS$1$0000; 
} 
+8

¿No es esto exactamente lo que dice ocdedio: finalmente se ejecuta después del cálculo del valor de retorno y antes de realmente regresar de la función? – mmmmmmmm

+5

Creo que la respuesta de Jon Skeet a continuación es más clara y precisa. –

+0

"bloque manejado por excepción" Creo que este escenario no tiene nada que ver con las excepciones y el manejo de excepciones. Se trata de cómo .NET implementa la construcción finalmente _resource_guard. –

18

La cláusula finally se ejecuta después de la declaración de devolución pero antes de regresar realmente de la función. Tiene poco que ver con la seguridad de los hilos, creo. No es un truco: al final se garantiza que siempre se ejecutará sin importar lo que hagas en tu bloque de prueba o en tu bloque de captura.

4

Si x es una variable local, no veo el punto, como x se establecerá de manera efectiva para anular todos modos cuando se sale del método y el valor del valor de retorno no es nulo (desde que se colocó en el registro antes de la llamada para establecer x en nulo).

Solo puedo ver que esto ocurra si desea garantizar el cambio del valor de un campo en el momento de la devolución (y después de determinar el valor de retorno).

+0

A menos que la variable local también sea capturada por un delegado :) –

+0

Umm, ¿qué ocurre entonces? –

+0

Luego hay un cierre, y el objeto no se puede recoger como basura, porque todavía hay una referencia. – recursive

305

La instrucción finally se ejecuta, pero el valor de retorno no se ve afectado. La orden de ejecución es:

  1. Código antes de la declaración de retorno se ejecuta
  2. Expresión en sentencia de retorno se evalúa
  3. finalmente se ejecuta el bloque
  4. Resultado evalúa en el paso 2 se devuelve

Aquí está un programa corto para demostrar:

using System; 

class Test 
{ 
    static string x; 

    static void Main() 
    { 
     Console.WriteLine(Method()); 
     Console.WriteLine(x); 
    } 

    static string Method() 
    { 
     try 
     { 
      x = "try"; 
      return x; 
     } 
     finally 
     { 
      x = "finally"; 
     } 
    } 
} 

Esto imprime "try" (porque eso es lo que se devuelve) y luego "finally" porque ese es el nuevo valor de x.

Por supuesto, si estamos devolviendo una referencia a un objeto mutable (p.un StringBuilder) entonces cualquier cambio realizado en el objeto en el bloque finally será visible a la vuelta; esto no ha afectado el valor de retorno en sí (que es solo una referencia).

+0

Me gustaría preguntar: ¿Existe alguna opción en Visual Studio para ver el lenguaje intermedio generado (IL) para el código C# escrito en el momento de la ejecución ... –

+0

Excepción a "si estamos devolviendo una referencia a un mutable" objeto (por ejemplo, un StringBuilder), cualquier cambio realizado en el objeto en el bloque finally será visible en el retorno "es si el objeto StringBuilder se establece en nulo en el bloque finally, en cuyo caso se devuelve un objeto no nulo. – Nick

+4

@Nick: Eso no es un cambio en el * objeto * - eso es un cambio en la * variable *. No afecta al objeto al que hace referencia el valor anterior de la variable. Entonces no, no es una excepción. –

10

Agregando a las respuestas dadas por Marc Gravell y Jon Skeet, es importante tener en cuenta que los objetos y otros tipos de referencia se comportan de manera similar cuando se devuelven pero tienen algunas diferencias.

el "qué" que es retornada sigue la misma lógica tipos simples:

class Test { 
    public static Exception AnException() { 
     Exception ex = new Exception("Me"); 
     try { 
      return ex; 
     } finally { 
      // Reference unchanged, Local variable changed 
      ex = new Exception("Not Me"); 
     } 
    } 
} 

La referencia que se está devolviendo ya ha sido evaluado antes de que se le asigna la variable local una nueva referencia en el bloque finally.

La ejecución es esencialmente:

class Test { 
    public static Exception AnException() { 
     Exception ex = new Exception("Me"); 
     Exception CS$1$0000 = null; 
     try { 
      CS$1$0000 = ex; 
     } finally { 
      // Reference unchanged, Local variable changed 
      ex = new Exception("Not Me"); 
     } 
     return CS$1$0000; 
    } 
} 

La diferencia es aún sería posible modificar los tipos mutables utilizando las propiedades/métodos del objeto que puede dar lugar a comportamientos inesperados si no se tiene cuidado.

class Test2 { 
    public static System.IO.MemoryStream BadStream(byte[] buffer) { 
     System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer); 
     try { 
      return ms; 
     } finally { 
      // Reference unchanged, Referenced Object changed 
      ms.Dispose(); 
     } 
    } 
} 

Un segundo aspecto a considerar acerca de tratar de retorno fin es que los parámetros pasados ​​"por referencia" todavía puede ser modificado después de la vuelta. Solo se ha evaluado el valor de retorno y se almacena en una variable temporal a la espera de ser devuelta, cualquier otra variable se sigue modificando de la manera normal. El contrato de un parámetro de salida puede incluso no cumplirse hasta que el bloque finalmente se bloquee de esta manera.

class ByRefTests { 
    public static int One(out int i) { 
     try { 
      i = 1; 
      return i; 
     } finally { 
      // Return value unchanged, Store new value referenced variable 
      i = 1000; 
     } 
    } 

    public static int Two(ref int i) { 
     try { 
      i = 2; 
      return i; 
     } finally { 
      // Return value unchanged, Store new value referenced variable 
      i = 2000; 
     } 
    } 

    public static int Three(out int i) { 
     try { 
      return 3; 
     } finally { 
      // This is not a compile error! 
      // Return value unchanged, Store new value referenced variable 
      i = 3000; 
     } 
    } 
} 

Al igual que cualquier otro flujo construir "tratar de retorno, finalmente," tiene su lugar y puede permitir por mirar código más limpio que escribir la estructura que realidad compila a. Pero debe usarse con cuidado para evitar gotcha's.

Cuestiones relacionadas