Menu

try catch en C# : gérer les exceptions avec catch, when et finally

try, catch et finally sont la façon dont C# gère les exceptions. Apprenez comment une exception remonte la pile d'appels, comment intercepter des types d'exception précis dans le bon ordre, filtrer avec when, nettoyer dans finally, relancer avec throw; sans perdre la trace de pile, et quelles exceptions ne pas intercepter.

Cette page contient des éditeurs exécutables - modifiez, exécutez et voyez la sortie instantanément.

Quand quelque chose tourne mal à l'exécution (un fichier manque, un texte n'est pas un nombre, une clé n'est pas dans un dictionnaire), .NET lève une exception : un objet qui décrit l'erreur. L'exception remonte la pile d'appels jusqu'à ce qu'un bloc catch la traite. Si rien ne la traite, le programme s'arrête et affiche l'erreur.

Un try catch simple

Enveloppez le code qui peut échouer dans try, et traitez l'échec dans catch :

Sortie :

42 doubled is 84
'forty-two' is not a number
7 doubled is 14
Still running

Quand int.Parse("forty-two") lève une exception, le Console.WriteLine qui le suit dans le bloc try est sauté, le bloc catch (FormatException) s'exécute, et la boucle continue. Vous pouvez omettre la variable (catch (FormatException)) quand vous n'avez pas besoin de l'objet exception.

Ce cas précis a un meilleur outil : int.TryParse(input, out int n) renvoie false au lieu de lever une exception. Les exceptions servent aux situations que le code n'attend pas ; une saisie souvent invalide est attendue, donc vérifiez-la au lieu d'intercepter.

Comment une exception remonte

Une exception levée au fond d'une chaîne d'appels déroule chaque méthode jusqu'à trouver un gestionnaire correspondant. Le code qui suit le point de levée dans chacune de ces méthodes ne s'exécute pas.

Sortie :

OrderTotal finished
5.00
Caught KeyNotFoundException in Main

PriceOf et OrderTotal n'ont pas de catch, donc l'exception les traverse directement jusqu'à Main. Placez un gestionnaire au niveau qui sait quoi faire de l'échec, ce qui n'est souvent pas là où il s'est produit.

Intercepter des exceptions précises, dans le bon ordre

Une clause catch traite son type et tous les types qui en dérivent. Quand il y a plusieurs clauses, la première qui correspond l'emporte, donc classez-les du plus spécifique au plus général :

Sortie :

10 / 2 = 5
Cannot divide by zero
Both values must be whole numbers
Unexpected: OverflowException

Le dernier appel lève OverflowException, qu'aucune clause spécifique ne traite, donc c'est le catch (Exception e) général qui s'en charge. Placer catch (Exception) en premier rendrait les clauses suivantes inaccessibles, et le compilateur le refuse avec l'erreur CS0160.

Filtres d'exception avec when

C# 6 a ajouté when : une condition qui décide si une clause catch s'applique. Si elle vaut false, le runtime continue de chercher un autre gestionnaire comme si la clause n'existait pas.

Sortie :

Not found: show an empty page
Server error 503: retry later
Unhandled by Call: HTTP 401

Les filtres permettent de choisir une branche selon des données contenues dans l'exception sans intercepter puis relancer. Ils sont aussi la façon propre de traiter de manière identique deux types sans rapport, comme le fait la troisième clause. Un filtre s'exécute avant le déroulement de la pile, donc un débogueur ou un vidage mémoire après plantage montre encore l'état d'origine quand aucun filtre ne correspond.

finally : du code qui s'exécute toujours

Un bloc finally s'exécute quand le contrôle quitte le try, qu'il se soit terminé, soit revenu plus tôt ou ait levé une exception. C'est là que va le nettoyage. (Une réserve : si rien n'intercepte l'exception nulle part, le processus peut se terminer sans l'exécuter.)

Sortie :

Open connection
Close connection
finished
Open connection
Close connection
returned early
Open connection
Close connection
handled error

Dans tous les cas, « Close connection » s'affiche avant que le résultat de la méthode n'atteigne Main : le finally s'exécute après le calcul de la valeur du return, mais avant que la méthode ne revienne réellement. Un try peut avoir un finally sans aucun catch, ce qui nettoie tout en laissant l'exception continuer vers l'appelant.

Pour les objets qui implémentent IDisposable (fichiers, flux, connexions), l'instruction using écrit ce try/finally pour vous.

Relancer : throw; ou throw e;

Parfois, un bloc catch journalise ou enregistre quelque chose puis laisse l'exception continuer. La façon de relancer détermine si la trace de pile survit :

Sortie :

throw;   trace mentions LoadConfig: True
throw e; trace mentions LoadConfig: False

throw e; traite l'exception comme nouvellement levée depuis le bloc catch, donc les cadres situés en dessous, y compris la méthode où l'erreur s'est produite, disparaissent de la trace. Relancez toujours avec un simple throw;. (L'attribut NoInlining n'est là que parce que le JIT pourrait fusionner une méthode aussi petite avec son appelant, ce qui la cacherait des deux traces.)

Pour ajouter du contexte, enveloppez plutôt l'exception dans une nouvelle et passez l'originale comme exception interne : throw new ConfigException("Could not start the app", e);. L'exception interne garde sa propre trace de pile, et les outils de journalisation affichent la chaîne. Écrire vos propres types d'exception est présenté dans lever des exceptions.

L'objet Exception

Toute exception dérive de System.Exception. Les membres que vous utiliserez le plus :

MembreCe qu'il contient
MessageUne description lisible par un humain
GetType().NameLe type d'exception, comme FormatException
StackTraceLa chaîne d'appels de méthodes au point de levée
InnerExceptionL'exception qui a causé celle-ci, ou null
ToString()Type, message, exceptions internes et trace de pile réunis

Journalisez e.ToString() plutôt que e.Message quand vous voulez diagnostiquer un échec plus tard : le message seul dit rarement où était le problème. Ne montrez pas e.ToString() aux utilisateurs finaux.

Types d'exception courants

ExceptionCause typique
NullReferenceExceptionAppeler un membre sur une référence null
ArgumentNullExceptionUne méthode a reçu null là où il lui faut une valeur
ArgumentOutOfRangeExceptionUn argument ou un index de liste est hors de la plage autorisée
IndexOutOfRangeExceptionUn index de tableau est hors de ses limites
FormatExceptionint.Parse, DateTime.Parse et consorts sur un texte au mauvais format
InvalidCastExceptionUn cast explicite vers un type que l'objet n'est pas
InvalidOperationExceptionL'objet n'est pas dans le bon état pour l'appel (séquence vide, collection modifiée)
KeyNotFoundExceptionLire avec l'indexeur une clé absente d'un dictionnaire
DivideByZeroExceptionDivision entière ou decimal par zéro
OverflowExceptionUn nombre analysé, une conversion checked ou un résultat arithmétique checked ne tient pas dans le type
FileNotFoundException, IOExceptionProblèmes de système de fichiers

NullReferenceException, IndexOutOfRangeException et InvalidCastException signalent presque toujours un bug. Corrigez le code plutôt que de les intercepter.

Ne pas avaler les exceptions

Un catch vide cache toutes les erreurs, y compris celles que vous n'aviez pas prévues :

try
{
    SaveOrder(order);
}
catch (Exception)
{
    // nothing: the order silently was not saved
}

Le programme continue comme si l'enregistrement avait fonctionné, et la vraie cause est perdue. Des règles qui gardent la gestion des exceptions honnête :

  • N'interceptez que ce que vous pouvez traiter, au niveau qui peut le traiter.
  • Si vous interceptez pour journaliser, relancez avec throw;, sauf si le programme peut vraiment continuer.
  • N'interceptez Exception qu'à la limite extérieure : Main, un gestionnaire de requêtes, une boucle de travail.
  • Préférez TryParse, TryGetValue et les tests de null aux exceptions pour les cas attendus. Lever une exception est lent comparé à un test, alors qu'un bloc try qui ne lève rien ne coûte presque rien.

Erreurs courantes

  • catch (Exception) en premier. Les clauses suivantes deviennent inaccessibles (CS0160).
  • throw e; pour relancer. Cela efface la trace de pile d'origine ; utilisez throw;.
  • Des blocs catch vides. Les erreurs disparaissent ; au minimum, journalisez et relancez.
  • Utiliser les exceptions pour le flux de contrôle. Validez la saisie avec TryParse au lieu d'intercepter FormatException.
  • Montrer aux utilisateurs le e.Message d'une exception du framework. Sa formulation change d'une version de .NET à l'autre et elle est écrite pour les développeurs.

Questions fréquentes

Comment fonctionne try catch en C# ?

Le code susceptible d'échouer va dans le bloc try. Si une instruction y lève une exception, le reste du bloc est sauté et le runtime cherche une clause catch dont le type correspond à l'exception, d'abord dans la méthode courante puis dans chaque appelant. Le premier catch correspondant s'exécute, et l'exécution reprend après toute l'instruction try.

finally s'exécute-t-il toujours en C# ?

Presque toujours : après que le bloc try se termine normalement, après qu'un catch a traité une exception, après un return ou un break dans le bloc, et quand une exception le traverse en remontant vers un catch plus haut dans la pile d'appels. Il ne s'exécute pas quand le processus se termine avant : un processus tué, Environment.FailFast, une StackOverflowException, et, sur .NET Core et plus, une exception que rien n'intercepte, qui termine le processus avant l'exécution des blocs finally.

Comment intercepter plusieurs exceptions en C# ?

Écrivez plusieurs clauses catch, le type le plus spécifique en premier : catch (FileNotFoundException) avant catch (IOException) avant catch (Exception). Le compilateur rejette une clause qui ne peut jamais être atteinte parce qu'une clause précédente intercepte déjà son type. Pour traiter de la même façon deux types sans rapport, utilisez un filtre : catch (Exception e) when (e is FormatException || e is OverflowException).

Quelle est la différence entre throw et throw ex en C# ?

Dans un catch, throw; relance l'exception courante avec sa trace de pile d'origine. throw ex; lève le même objet mais réinitialise la trace de pile à la ligne courante, donc la méthode où l'erreur s'est réellement produite disparaît de la trace. Utilisez throw;, ou enveloppez l'exception : throw new MyException("context", ex);.

Qu'est-ce qu'un filtre d'exception en C# ?

Une clause when après un catch (C# 6 et plus) : catch (HttpRequestException e) when (e.Message.Contains("404")). Le bloc catch ne s'exécute que si la condition vaut true ; sinon l'exception continue de chercher un autre gestionnaire comme si la clause n'existait pas, et sa pile n'est pas déroulée.

Faut-il intercepter Exception en C# ?

Seulement aux limites d'un programme : le haut de Main, un gestionnaire de requêtes ou une boucle en arrière-plan, où le rôle est de journaliser l'erreur puis de continuer ou de s'arrêter proprement. En profondeur dans le code, interceptez les types précis que vous pouvez réellement traiter, et laissez tout le reste se propager.

Coddy programming languages illustration

Apprendre à coder avec Coddy

COMMENCER