¿Cuándo se ejecuta finalmente si lanzas una excepción desde el bloque catch?


135
try {
   // Do stuff
}
catch (Exception e) {
   throw;
}
finally {
   // Clean up
}

En el bloque anterior, ¿cuándo se llama el bloque finalmente? Antes del lanzamiento de e o finalmente se llama y luego atrapar?


14
ps no deberías "tirar e"; porque eso estropeará el seguimiento de la pila de la excepción original. Deberías simplemente "tirar"; O cree una nueva excepción y establezca la InnerException en "e" antes de lanzarla.
Erv Walter el

24
finalmente sería una elección de palabra clave bastante pobre si no se ejecutara al final , ¿no crees?
Eric Lippert

@ErvWalter, ¿sigue siendo cierto? Lo estoy probando en ambos sentidos en VS2017, y parece ser exactamente lo mismo. ¿Podría proporcionar más información o una referencia? Gracias
Jeff Puckett

solo sugerencia de nombre use Excepción ex - reserva e para eventos / delegados
Sr. R

Respuestas:


138

Se llamaría después de que e se vuelva a lanzar (es decir, después de que se ejecute el bloque catch)

editando esto 7 años después: una nota importante es que si eun bloque try / catch no lo atrapa más arriba en la pila de llamadas o lo maneja un controlador de excepción global, entonces el finallybloque puede nunca ejecutarse en absoluto.


18
y nunca si llamas a Envrionment.FailFast ()
Johannes Rudolph

16
Después de probar el código en la respuesta de Brandon, veo que el finallyno se ejecuta si la excepción lanzada en el anterior catchnunca es atrapado en una externa try- catchbloque!
Andrew

3
Gracias por editar su respuesta (aceptada) para incluir la nueva información.
Gordon Bean

3
Ellos (Microsoft) hablan de ello en el nuevo sitio de documentación: docs.microsoft.com/en-us/dotnet/csharp/language-reference/… : "Dentro de una excepción manejada, el bloque finalmente asociado está garantizado para ejecutarse. Sin embargo , si la excepción no se maneja, la ejecución del último bloque depende de cómo se active la operación de desenrollado de la excepción. Eso, a su vez, depende de cómo esté configurada la computadora ".
DotNetSparky

1
Tenga en cuenta que "atrapado por un bloque try / catch más arriba en la pila de llamadas" incluirá controladores de marco como los de ASP.NET o un corredor de prueba. Una mejor manera de decirlo podría ser "si su programa continúa ejecutándose después del bloque catch, entonces el bloque finalmente se ejecutará".
ArrowCase

91

¿Por qué no probarlo?

outer try
inner try
inner catch
inner finally
outer catch
outer finally

con código (formateado para espacio vertical):

static void Main() {
    try {
        Console.WriteLine("outer try");
        DoIt();
    } catch {
        Console.WriteLine("outer catch");
        // swallow
    } finally {
        Console.WriteLine("outer finally");
    }
}
static void DoIt() {
    try {
        Console.WriteLine("inner try");
        int i = 0;
        Console.WriteLine(12 / i); // oops
    } catch (Exception e) {
        Console.WriteLine("inner catch");
        throw e; // or "throw", or "throw anything"
    } finally {
        Console.WriteLine("inner finally");
    }
}

55
+1, para algo tan simple, deberías probarlo como lo ha hecho Marc. GJ lo ilustra con try / catch / finalmente anidado :)
Allen Rice

1
@AllenRice, ¿por qué intentarlo si Marc ya lo ha hecho y puedo buscar en Google para encontrar la respuesta de Marc? O tal vez mejor, pruébelo usted mismo, luego cree una pregunta SO y responda usted mismo para beneficio de los demás.
joshden

8
Tenga en cuenta que si no detecta la excepción en la captura externa, ¡la interna finalmente NUNCA se ejecuta! En ese caso, la salida esouter try inner try inner catch Unhandled Exception: System.DivideByZeroException...
Andrew

1
@ Andrew tienes razón. La explicación que puede encontrar aquí MSDN Magazine 2008 Septiembre: Procesamiento de excepciones no controladas en el CLR (para abrir chm necesita desbloquear: Propiedades del archivo -> General -> Desbloquear). Si reemplaza el bloque catch externo con "catch (ArgumentException)", finalmente no se continuará con el bloqueo, porque CLR no puede encontrar ningún "controlador de excepciones que haya aceptado manejar la excepción" DivideByZeroException.
Vladimir

35

Después de leer todas las respuestas aquí, parece que la respuesta final es : depende :

  • Si vuelve a lanzar una excepción dentro del bloque catch, y esa excepción se captura dentro de otro bloque catch, todo se ejecuta de acuerdo con la documentación.

  • Sin embargo, si la excepción re-trown no se maneja, finalmente nunca se ejecuta.

Probé este ejemplo de código en VS2010 con C # 4.0

static void Main()
    {
        Console.WriteLine("Example 1: re-throw inside of another try block:");

        try
        {
            Console.WriteLine("--outer try");
            try
            {
                Console.WriteLine("----inner try");
                throw new Exception();
            }
            catch
            {
                Console.WriteLine("----inner catch");
                throw;
            }
            finally
            {
                Console.WriteLine("----inner finally");
            }
        }
        catch
        {
            Console.WriteLine("--outer catch");
            // swallow
        }
        finally
        {
            Console.WriteLine("--outer finally");
        }
        Console.WriteLine("Huzzah!");

        Console.WriteLine();
        Console.WriteLine("Example 2: re-throw outside of another try block:");
        try
        {
            Console.WriteLine("--try");
            throw new Exception();
        }
        catch
        {
            Console.WriteLine("--catch");
            throw;
        }
        finally
        {
            Console.WriteLine("--finally");
        }

        Console.ReadLine();
    }

Aquí está la salida:

Ejemplo 1: volver a tirar dentro de otro bloque de prueba:
--probar exterior
---- prueba
interna
---- captura interna ---- interior finalmente
- captura
externa --alterno finalmente
¡Huzzah!

Ejemplo 2: volver a lanzar fuera de otro bloque try:
--try
--catch

Excepción no controlada: System.Exception: se produjo una excepción del tipo 'System.Exception'.
en ConsoleApplication1.Program.Main () en C: \ local source \ ConsoleApplication1 \ Program.cs: línea 53


3
Gran captura, no estaba al tanto de eso!
Andrew

1
Tenga en cuenta que el último finalmente puede ejecutarse, dependiendo de lo que elija: stackoverflow.com/a/46267841/480982
Thomas Weller

1
Interesante ... En .NET Core 2.0, la parte finalmente se ejecuta después de la excepción no controlada.
Mahdi Ghiasi

Curiosamente, acabo de hacer una prueba tanto en .NET Core 2.0 como en .NET Framework 4.6.1 y ambos corren finalmente después de la excepción no controlada. ¿Ha cambiado este comportamiento?
Cameron Bielstein el

24

Su ejemplo se comportaría de manera idéntica a este código:

try {
    try {
        // Do stuff
    } catch(Exception e) {
        throw e;
    }
} finally {
    // Clean up
}

Como nota al margen, si realmente quiere decir throw e;(es decir, lanzar la misma excepción que acaba de atrapar), es mucho mejor hacerlo throw;, ya que eso preservará el rastro original de la pila en lugar de crear uno nuevo.


No creo que esto sea correcto. Finalmente debería estar dentro del bloque de prueba exterior, no fuera de él
Matthew Pigram

@MatthewPigram: ¿Qué quieres decir? De hecho, el finallybloque se ejecutará después del catchbloque (incluso si el bloque catch vuelve a generar la excepción), que es lo que mi fragmento está tratando de ilustrar.
Daniel Pryden

Por cómo interpreto su ejemplo, está intentando hacer un intento de captura finalmente dentro de otro bloque de prueba. NO una captura de prueba dentro de una captura de prueba finalmente
Matthew Pigram

1
@MatthewPigram: Mi respuesta no tiene ninguna construcción "try-catch-finally" en absoluto. Tiene un "try-finally", y dentro del trybloque de mi respuesta tiene un "try-catch". Estoy tratando de explicar el comportamiento de la construcción de 3 partes usando dos construcciones de 2 partes. No veo ningún signo de un segundo trybloque en la pregunta original, así que no entiendo de dónde lo sacas.
Daniel Pryden

12

Si hay una excepción no controlada dentro de un bloque controlador de captura, el bloque finalmente se llama exactamente cero veces

  static void Main(string[] args)
  {
     try
     {
        Console.WriteLine("in the try");
        int d = 0;
        int k = 0 / d;
     }
     catch (Exception e)
     {
        Console.WriteLine("in the catch");
        throw;
     }
     finally
     {
        Console.WriteLine("In the finally");
     }
  }

Salida:

C: \ usuarios \ administrador \ documentos \ TestExceptionNesting \ bin \ Release> TestExceptionNesting.exe

en el intento

en la captura

Excepción no controlada: System.DivideByZeroException: intento de dividir por cero. en TestExceptionNesting.Program.Main (String [] args) en C: \ users \ administrador \ documentos \ TestExceptionNesting \ TestExceptionNesting.cs: línea 22

C: \ usuarios \ administrador \ documentos \ TestExceptionNesting \ bin \ release>

Me hicieron esta pregunta hoy en una entrevista y el entrevistador continuó diciendo "¿estás seguro de que finalmente no me llamen?" No estaba seguro de si se trataba de una pregunta capciosa o si el entrevistador tenía algo más en mente y escribió el código incorrecto para que lo depurara, así que llegué a casa y lo intenté (compilar y ejecutar, sin interacción del depurador), solo para pensar descanso.


A menos que la excepción lanzada se encuentre en otra captura en algún lugar más arriba de la pila, en cuyo caso puede ejecutarse si se maneja esa excepción lanzada ... O podría estar equivocado ...
tomosius

@tomosius, sí, eso es lo que explica la respuesta de Brandon. :)
Andrew

@tomosius Es por eso que comencé especificando "Si hay una excepción no controlada". Si la excepción lanzada se detecta en algún lugar, entonces, por definición, estamos hablando de un caso diferente.
Eusebio Rufian-Zilbermann

Esto no es verdad. Al menos no para NET Core 3.1. Un nuevo proyecto de consola simple con este código muestra "en el finalmente" después de la excepción.
emzero

Interesante que el comportamiento haya cambiado, .NET core ni siquiera existía cuando publiqué;)
Eusebio Rufian-Zilbermann

2

Una forma sencilla de saber también es depurar el código y notar cuándo finalmente se llama.


1

Al probar con una aplicación de consola C #, el código finalmente se ejecutó después de que se lanzó la excepción: existió el "Diálogo de error de la aplicación" y después de elegir la opción "Cerrar el programa", el bloque finalmente se ejecutó en esa ventana de consola. Pero estableciendo el punto de ruptura dentro del bloque de código finalmente, nunca puedo golpearlo. El depurador sigue deteniéndose en la declaración de lanzamiento. Aquí está mi código de prueba:

    class Program
    {
       static void Main(string[] args)
       {
          string msg;
          Console.WriteLine(string.Format("GetRandomNuber returned: {0}{1}", GetRandomNumber(out msg), msg) == "" ? "" : "An error has occurred: " + msg);
       }

       static int GetRandomNumber(out string errorMessage)
       {
         int result = 0;
         try
         {
            errorMessage = "";
            int test = 0;
            result = 3/test;
            return result;
         }
         catch (Exception ex)
         {
            errorMessage = ex.Message;
            throw ex;

         }
         finally
         {
            Console.WriteLine("finally block!");
         }

       }
    }

Depuración en VS2010 - .NET Framework 4.0

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.