web-dev-qa-db-fra.com

Que se passe-t-il si un bloc finally lance une exception?

Si un bloc finally lève une exception, que se passe-t-il exactement ?

Plus précisément, que se passe-t-il si l'exception est renvoyée à mi-chemin d'un bloc final? Les autres déclarations (après) de ce bloc sont-elles invoquées?

Je suis conscient que les exceptions se propageront vers le haut.

251
Jack Kada

Si un bloc finally lève une exception, que se passe-t-il exactement ?

Cette exception se propage de plus en plus loin et sera (peut) être gérée à un niveau supérieur.

Votre bloc final ne sera pas au-delà du point où l'exception est levée.

Si le bloc finally s'exécutait lors du traitement d'une exception précédente, cette première exception est perdue.

Spécification de langage C # 4 § 8.9.5: Si le bloc finally émet une autre exception, le traitement de l'exception en cours est terminé.

404
Henk Holterman

Pour des questions comme celles-ci, j'ouvre généralement un projet d'application console vide dans Visual Studio et écris un petit exemple de programme:

using System;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("exception thrown from try block");
            }
            catch (Exception ex)
            {
                Console.WriteLine("Inner catch block handling {0}.", ex.Message);
                throw;
            }
            finally
            {
                Console.WriteLine("Inner finally block");
                throw new Exception("exception thrown from finally block");
                Console.WriteLine("This line is never reached");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Outer catch block handling {0}.", ex.Message);
        }
        finally
        {
            Console.WriteLine("Outer finally block");
        }
    }
}

Lorsque vous exécutez le programme, vous verrez l'ordre exact dans lequel les blocs catch et finally sont exécutés. Veuillez noter que le code du bloc finally après le lancement de l'exception ne sera pas exécuté (en fait, dans cet exemple de programme, Visual Studio vous avertira même qu'il a détecté du code inaccessible):

 Exception de gestion du bloc de capture interne levée du bloc try. 
 Bloc final interne 
 Exception de manipulation du bloc de capture externe levée du bloc finally. 
 Bloc final finalement 

Remarque complémentaire

Comme Michael Damatov l'a fait remarquer, une exception du bloc try sera "mangée" si vous ne la gérez pas dans un bloc (interne) catch. En fait, dans l'exemple ci-dessus, l'exception relative à la relance n'apparaît pas dans le bloc de capture externe. Pour rendre cela encore plus clair, regardez l'échantillon suivant légèrement modifié:

using System;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("exception thrown from try block");
            }
            finally
            {
                Console.WriteLine("Inner finally block");
                throw new Exception("exception thrown from finally block");
                Console.WriteLine("This line is never reached");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine("Outer catch block handling {0}.", ex.Message);
        }
        finally
        {
            Console.WriteLine("Outer finally block");
        }
    }
}

Comme vous pouvez le voir à la sortie, l'exception interne est "perdue" (c'est-à-dire ignorée):

 Bloc interne final 
 Exception de gestion du bloc de capture externe levée à partir du dernier bloc. 
 Bloc final finalement 
96
Dirk Vollmar

S'il existe une exception en attente (lorsque le bloc try a un finally mais pas catch), la nouvelle exception le remplace.

S'il n'y a pas d'exception en attente, cela fonctionne comme si une exception était levée en dehors du bloc finally.

10
Guffa

L'exception est propagée.

4
Darin Dimitrov

Snippet rapide (et plutôt évident) pour enregistrer "l'exception originale" (jetée dans le bloc try) et sacrifier "enfin l'exception" (le bloc finally), au cas où l'original serait plus important pour toi:

try
{
    throw new Exception("Original Exception");
}
finally
{
    try
    {
        throw new Exception("Finally Exception");
    }
    catch
    { }
}

Lorsque le code ci-dessus est exécuté, "Original Exception" se propage dans la pile d'appels et "Finally Exception" est perdu.

2
lxa

Je devais le faire pour détecter une erreur en essayant de fermer un flux qui n'a jamais été ouvert à cause d'une exception.

errorMessage = string.Empty;

try
{
    byte[] requestBytes = System.Text.Encoding.ASCII.GetBytes(xmlFileContent);

    webRequest = WebRequest.Create(url);
    webRequest.Method = "POST";
    webRequest.ContentType = "text/xml;charset=utf-8";
    webRequest.ContentLength = requestBytes.Length;

    //send the request
    using (var sw = webRequest.GetRequestStream()) 
    {
        sw.Write(requestBytes, 0, requestBytes.Length);
    }

    //get the response
    webResponse = webRequest.GetResponse();
    using (var sr = new StreamReader(webResponse.GetResponseStream()))
    {
        returnVal = sr.ReadToEnd();
        sr.Close();
    }
}
catch (Exception ex)
{
    errorMessage = ex.ToString();
}
finally
{
    try
    {
        if (webRequest.GetRequestStream() != null)
            webRequest.GetRequestStream().Close();
        if (webResponse.GetResponseStream() != null)
            webResponse.GetResponseStream().Close();
    }
    catch (Exception exw)
    {
        errorMessage = exw.ToString();
    }
}

si WebRequest a été créé mais qu’une erreur de connexion s’est produite pendant la

using (var sw = webRequest.GetRequestStream())

alors, finalement, une exception tenterait de fermer les connexions qu’elle pensait être ouvertes parce que la requête WebRequest avait été créée.

Si le programme n'avait finalement pas de try-catch à l'intérieur, ce code provoquerait une exception non gérée lors du nettoyage de la requête Web.

if (webRequest.GetRequestStream() != null) 

à partir de là, le code se fermerait sans traiter correctement l'erreur qui s'est produite et donc causer des problèmes à la méthode d'appel.

J'espère que cela aide comme exemple

2
Emma Grant
public void MyMethod()
{
   try
   {
   }
   catch{}
   finally
   {
      CodeA
   }
   CodeB
}

La manière dont les exceptions levées par CodeA et CodeB sont traitées est la même.

Une exception levée dans un bloc finally n'a rien de spécial, considérez-la comme l'exception levée par le code B.

1
Cheng Chen

Le lancement d'une exception alors qu'une autre exception est active entraînera le remplacement de la première exception par la deuxième exception (ultérieure).

Voici un code qui illustre ce qui se passe:

    public static void Main(string[] args)
    {
        try
        {
            try
            {
                throw new Exception("first exception");
            }
            finally
            {
                //try
                {
                    throw new Exception("second exception");
                }
                //catch (Exception)
                {
                    //throw;
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }
  • Exécutez le code et vous verrez "deuxième exception"
  • Décommentez les instructions try and catch et vous verrez "première exception"
  • Décommentez également le lancer; déclaration et vous verrez "deuxième exception" à nouveau.
1
Doug Coburn

L'exception se propage et doit être gérée à un niveau supérieur. Si l'exception n'est pas gérée au niveau supérieur, l'application se bloque. L'exécution du bloc "finally" s'arrête au point où l'exception est levée.

Que ce soit une exception ou non, le blocage "enfin" est garanti.

  1. Si le bloc "finally" est en cours d'exécution après qu'une exception se soit produite dans le bloc try,

  2. et si cette exception n'est pas gérée

  3. et si le bloc enfin lève une exception

Ensuite, l'exception d'origine qui s'est produite dans le bloc try est perdue.

public class Exception
{
    public static void Main()
    {
        try
        {
            SomeMethod();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }

    public static void SomeMethod()
    {
        try
        {
            // This exception will be lost
            throw new Exception("Exception in try block");
        }
        finally
        {
            throw new Exception("Exception in finally block");
        }
    }
} 

Grand article pour les détails

1
Raj Baral

Il y a quelques mois, j'ai aussi fait face à quelque chose comme ça,

    private  void RaiseException(String errorMessage)
    {
        throw new Exception(errorMessage);
    }

    private  void DoTaskForFinally()
    {
        RaiseException("Error for finally");
    }

    private  void DoTaskForCatch()
    {
        RaiseException("Error for catch");
    }

    private  void DoTaskForTry()
    {
        RaiseException("Error for try");
    }


        try
        {
            /*lacks the exception*/
            DoTaskForTry();
        }
        catch (Exception exception)
        {
            /*lacks the exception*/
            DoTaskForCatch();
        }
        finally
        {
            /*the result exception*/
            DoTaskForFinally();
        }

Pour résoudre un tel problème, j'ai créé une classe d'utilitaires comme

class ProcessHandler : Exception
{
    private enum ProcessType
    {
        Try,
        Catch,
        Finally,
    }

    private Boolean _hasException;
    private Boolean _hasTryException;
    private Boolean _hasCatchException;
    private Boolean _hasFinnallyException;

    public Boolean HasException { get { return _hasException; } }
    public Boolean HasTryException { get { return _hasTryException; } }
    public Boolean HasCatchException { get { return _hasCatchException; } }
    public Boolean HasFinnallyException { get { return _hasFinnallyException; } }
    public Dictionary<String, Exception> Exceptions { get; private set; } 

    public readonly Action TryAction;
    public readonly Action CatchAction;
    public readonly Action FinallyAction;

    public ProcessHandler(Action tryAction = null, Action catchAction = null, Action finallyAction = null)
    {

        TryAction = tryAction;
        CatchAction = catchAction;
        FinallyAction = finallyAction;

        _hasException = false;
        _hasTryException = false;
        _hasCatchException = false;
        _hasFinnallyException = false;
        Exceptions = new Dictionary<string, Exception>();
    }


    private void Invoke(Action action, ref Boolean isError, ProcessType processType)
    {
        try
        {
            action.Invoke();
        }
        catch (Exception exception)
        {
            _hasException = true;
            isError = true;
            Exceptions.Add(processType.ToString(), exception);
        }
    }

    private void InvokeTryAction()
    {
        if (TryAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasTryException, ProcessType.Try);
    }

    private void InvokeCatchAction()
    {
        if (CatchAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasCatchException, ProcessType.Catch);
    }

    private void InvokeFinallyAction()
    {
        if (FinallyAction == null)
        {
            return;
        }
        Invoke(TryAction, ref _hasFinnallyException, ProcessType.Finally);
    }

    public void InvokeActions()
    {
        InvokeTryAction();
        if (HasTryException)
        {
            InvokeCatchAction();
        }
        InvokeFinallyAction();

        if (HasException)
        {
            throw this;
        }
    }
}

Et utilisé comme ça

try
{
    ProcessHandler handler = new ProcessHandler(DoTaskForTry, DoTaskForCatch, DoTaskForFinally);
    handler.InvokeActions();
}
catch (Exception exception)
{
    var processError = exception as ProcessHandler;
    /*this exception contains all exceptions*/
    throw new Exception("Error to Process Actions", exception);
}

mais si vous voulez utiliser les paramètres et les types de retour c'est une autre histoire

1
Dipon Roy