Quand est finalement exécuté si vous lancez une exception du bloc catch?


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

Dans le bloc ci-dessus, quand le bloc finally est-il appelé? Avant le lancer de e ou est finalement appelé et ensuite attraper?


14
ps vous ne devriez pas "jeter e;" car cela va gâcher la trace de pile de l'exception d'origine. Vous devriez simplement "lancer;". Ou créez une nouvelle exception et définissez InnerException sur "e" avant de la lancer.
Erv Walter

24
enfin, ce serait un choix assez médiocre de mot-clé s'il ne fonctionnait pas en dernier , n'est-ce pas?
Eric Lippert

@ErvWalter est-ce toujours vrai? Je le teste dans les deux sens dans VS2017, et il semble que ce soit exactement la même chose. Pourriez-vous fournir plus d'informations ou une référence? Merci
Jeff Puckett

juste nommer la suggestion utiliser Exception ex - réserver e pour les événements / délégués
mr R

Réponses:


138

Il serait appelé après que e soit relancé (c'est-à-dire après l'exécution du bloc catch)

éditer cela 7 ans plus tard - une note importante est que s'il en'est pas intercepté par un bloc try / catch plus haut dans la pile d'appels ou géré par un gestionnaire d'exceptions global, alors le finallybloc peut ne jamais s'exécuter du tout.


18
et jamais si vous appelez Envrionment.FailFast ()
Johannes Rudolph

16
Après avoir essayé le code dans la réponse de Brandon, je vois que l' finallyest pas exécuté si l'exception levée dans le précédent catchest jamais pris dans un extérieur try- catchbloc!
Andrew

3
Merci d'avoir modifié votre réponse (acceptée) pour inclure les nouvelles informations.
Gordon Bean

3
Ils (Microsoft) en parlent sur le nouveau site de documentation: docs.microsoft.com/en-us/dotnet/csharp/language-reference/… : "Dans une exception gérée, le bloc finally associé est garanti d'être exécuté. Cependant , si l'exception n'est pas gérée, l'exécution du bloc finally dépend de la façon dont l'opération de déroulement de l'exception est déclenchée. Cela, à son tour, dépend de la configuration de votre ordinateur. "
DotNetSparky

1
Notez que "intercepté par un bloc try / catch plus haut dans la pile d'appels" inclura des gestionnaires de framework tels que ceux d'ASP.NET ou d'un exécuteur de test. Une meilleure façon de le dire pourrait être "si votre programme continue de s'exécuter après le bloc catch, alors le bloc finally s'exécutera".
ArrowCase

91

Pourquoi ne pas l'essayer:

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

avec code (formaté pour un espace 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");
    }
}

5
+1, pour quelque chose d'aussi simple, vous devriez vraiment l'essayer comme Marc l'a fait. GJ l'illustre avec try / catch / finally imbriqué :)
Allen Rice

1
@AllenRice pourquoi l'essayer si Marc l'a déjà fait, et je peux simplement chercher sur Google la réponse de Marc? Ou peut-être mieux, essayez vous-même, puis créez une question SO et répondez-y vous-même pour le bénéfice des autres.
joshden

8
Veuillez noter que si vous n'attrapez pas l'exception dans la capture externe, l'interne n'est finalement JAMAIS exécutée !! Dans ce cas, la sortie estouter try inner try inner catch Unhandled Exception: System.DivideByZeroException...
Andrew

1
@Andrew vous avez raison. L'explication que vous pouvez trouver ici MSDN Magazine Septembre 2008: Traitement des exceptions non gérées dans le CLR (pour ouvrir chm, il faut déverrouiller: Propriétés du fichier -> Général -> Déverrouiller). Si vous remplacez le bloc catch externe par "catch (ArgumentException)", aucun bloc final ne sera également exécuté, car CLR ne peut trouver aucun "gestionnaire d'exceptions ayant accepté de gérer l'exception" DivideByZeroException.
vladimir

35

Après avoir lu toutes les réponses ici, il semble que la réponse finale est que cela dépend :

  • Si vous relancez une exception dans le bloc catch et que l'exception est interceptée à l'intérieur d'un autre bloc catch, tout s'exécute selon la documentation.

  • Cependant, si l'exception re-trown n'est pas gérée, le paramètre finally ne s'exécute jamais.

J'ai testé cet exemple de code dans VS2010 avec 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();
    }

Voici la sortie:

Exemple 1: relancez l'intérieur d'un autre bloc try:
--outer try
---- inner try
---- inner catch
----
inside enfin --outer catch
--outer enfin
Huzzah!

Exemple 2: relancez en dehors d'un autre bloc try:
--try
--catch

Exception non gérée: System.Exception: une exception de type «System.Exception» a été levée.
à ConsoleApplication1.Program.Main () dans C: \ source locale \ ConsoleApplication1 \ Program.cs: ligne 53


3
Super prise, je n'étais pas au courant de ça!
Andrew

1
Notez que le dernier peut enfin s'exécuter, selon ce que vous choisissez: stackoverflow.com/a/46267841/480982
Thomas Weller

1
Intéressant ... Sur .NET Core 2.0, la partie finally s'exécute après l'exception non gérée.
Mahdi Ghiasi

Il est intéressant de noter que je viens de faire un test dans .NET Core 2.0 et .NET Framework 4.6.1 et ils exécutent tous les deux enfin après l'exception non gérée. Ce comportement a-t-il changé?
Cameron Bielstein

24

Votre exemple se comporterait de la même manière que ce code:

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

Comme une note de côté, si vous avez vraiment moyen throw e;, il est (qui est, jetez la même exception que vous venez de prendre) beaucoup mieux pour le faire throw;, car cela préservera la trace de la pile d' origine au lieu de créer un nouveau.


Je ne pense pas que ce soit correct. Enfin devrait être à l'intérieur du bloc d'essai extérieur, pas à l'extérieur
Matthew Pigram

@MatthewPigram: Que voulez-vous dire? Le finallybloc fonctionnera en fait après le catchbloc (même si le bloc catch relance l'exception), ce que mon extrait de code tente d'illustrer.
Daniel Pryden

d'après la façon dont j'interprète son exemple, il essaie de faire un try catch enfin dans un autre bloc try. PAS une prise d'essai dans une prise d'essai enfin
Matthew Pigram

1
@MatthewPigram: Ma réponse n'a pas du tout de construction "try-catch-finally". Il a un "try-finally", et à l'intérieur du trybloc de cela ma réponse a un "try-catch". J'essaie d'expliquer le comportement de la construction en 3 parties en utilisant deux constructions en 2 parties. Je ne vois aucun signe de deuxième tryblocage dans la question initiale, donc je ne comprends pas d'où vous venez.
Daniel Pryden

12

S'il y a une exception non gérée dans un bloc catch handler, le bloc finally est appelé exactement zéro fois

  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");
     }
  }

Production:

C: \ users \ administrator \ documents \ TestExceptionNesting \ bin \ Release> TestExceptionNesting.exe

dans l'essai

dans la prise

Exception non gérée: System.DivideByZeroException: tentative de division par zéro. à TestExceptionNesting.Program.Main (String [] args) dans C: \ users \ administrator \ documents \ TestExceptionNesting \ TestExceptionNesting.cs: ligne 22

C: \ users \ administrator \ documents \ TestExceptionNesting \ bin \ release>

On m'a posé cette question aujourd'hui lors d'une interview et l'intervieweur a continué à revenir "êtes-vous sûr que finalement ne sera pas appelé?" Je ne savais pas s'il s'agissait d'une question piège ou si l'intervieweur avait autre chose en tête et a écrit le mauvais code à déboguer, alors je suis rentré à la maison et l'ai essayé (construire et exécuter, pas d'interaction avec le débogueur), juste pour me mettre à l'esprit du repos.


Sauf si l'exception levée est interceptée dans une autre capture quelque part plus haut dans la pile, auquel cas elle peut s'exécuter si cette exception levée est gérée ... Ou je me trompe peut-être ...
tomosius

@tomosius, oui, c'est ce que la réponse de Brandon explique. :)
Andrew

@tomosius C'est pourquoi j'ai commencé par spécifier "S'il y a une exception non gérée". Si l'exception levée est interceptée quelque part, nous parlons par définition d'un cas différent.
Eusebio Rufian-Zilbermann

Ce n'est pas vrai. Du moins pas pour NET Core 3.1. Un nouveau projet de console simple avec ce code montre "in the finally" après l'exception.
emzero

Intéressant que le comportement ait changé, .NET core n'existait même pas lorsque j'ai posté;)
Eusebio Rufian-Zilbermann

2

Un moyen simple de le dire est également de déboguer votre code et de remarquer quand enfin est appelé.


1

Test avec une application console C #, le code finally a été exécuté après la levée de l'exception: La "boîte de dialogue d'erreur d'application" existait et après avoir choisi l'option "Fermer le programme", le bloc finally a été exécuté dans cette fenêtre de console. Mais en définissant le point de rupture à l'intérieur du bloc de code finally, je ne peux jamais l'atteindre. Le débogueur continue de s'arrêter à l'instruction throw. Voici mon code de test:

    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!");
         }

       }
    }

Débogage dans VS2010 - .NET Framework 4.0

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.