Lo que realmente sucede en una prueba {return x; } finalmente {x = null; } statement?

Vi esta sugerencia en otra pregunta y me preguntaba si alguien podría explicarme cómo funciona esto.

try { return x; } finally { x = null; } 

Quiero decir, ¿la cláusula finally se ejecuta realmente después de la statement de return ? ¿Cuán inseguro es este código? ¿Puedes pensar en cualquier hackery adicional que se pueda hacer con este try-finally hack?

No, en el nivel de IL, no puede regresar desde el interior de un bloque manejado por excepción. En esencia, lo almacena en una variable y luego regresa

es decir, similar a:

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

por ejemplo (usando reflector):

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

comstack 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 } 

Esto básicamente declara una variable local ( CS$1$0000 ), coloca el valor en la variable (dentro del bloque manejado), luego de salir del bloque carga la variable, luego la devuelve. Reflector lo representa como:

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

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

  1. Se ejecuta el código antes de la statement de devolución
  2. Expresión en statement de retorno es evaluada
  3. finalmente se ejecuta el bloque
  4. El resultado evaluado en el paso 2 es devuelto

Aquí hay un progtwig 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 (por ejemplo, un StringBuilder), cualquier cambio realizado en el objeto en el bloque finally será visible a la vuelta; esto no ha afectado al valor de retorno en sí (que es solo un referencia).

La cláusula finally se ejecuta después de la statement return, 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.

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

El “Qué” que se devuelve sigue la misma lógica que los 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 se ha evaluado antes de que a la variable local se le asigne 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 que aún sería posible modificar los tipos mutables utilizando las propiedades / métodos del objeto que pueden dar lugar a comportamientos inesperados si no 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(); } } } 

Una segunda cosa a considerar sobre try-return-finally es que los parámetros pasados ​​”por referencia” aún se pueden modificar después de la devolución. Solo se ha evaluado el valor de retorno y se almacena en una variable temporal que espera 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 constructo de flujo, “try-return-finally” tiene su lugar y puede permitir un código de aspecto más limpio que escribir la estructura en la que realmente se comstack. Pero debe usarse con cuidado para evitar gotcha’s.

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

Solo puedo ver que esto ocurra si desea garantizar el cambio del valor de un campo al regresar (y después de que se determine el valor de retorno).