Instructions de gestion des exceptions

Références

L'actualité

Librairie

L'information

Introduction

C# fournit la prise en charge intégrée de la gestion des situations anormales, appelées exceptions, qui peuvent se produire pendant l'exécution de votre programme. Ces exceptions sont gérées par du code en dehors du flux de contrôle normal. Les rubriques de gestion des exceptions suivantes sont décrites dans cette section :

  • throw
  • try-catch
  • try-finally
  • try-catch-finally

throw

Signale l'occurrence d'une exception pendant l'exécution du programme.

Notes

La syntaxe de throw est :

e est une instance d'une classe dérivée de System.Exception. L'exemple suivant utilise l'instruction throw pour lever une exception IndexOutOfRangeException si l'argument passé à une méthode nommée GetNumber ne correspond pas à un index valide d'un tableau interne.

Les appelants de méthode utilisent alors un bloc try-catch ou try-catch-finally pour gérer l'exception levée. L'exemple suivant gère l'exception levée par la méthode GetNumber.

Génération répétée d'une exception

throw peut également être utilisé dans un bloc catch pour lever de nouveau une exception gérée dans un bloc catch. Dans ce cas, throw n'accepte pas d'opérande d'exception. Cela est très utile lorsqu'une méthode passe un argument d'un appelant à une autre méthode de bibliothèque, et que la méthode de bibliothèque lève une exception qui doit être passée à l'appelant. Par exemple, l'exemple suivant lève de nouveau une exception NullReferenceException qui est levée lorsque vous tentez de récupérer le premier caractère d'une chaîne non initialisée.


Vous pouvez également utiliser la syntaxe throw e dans un bloc catch pour instancier une nouvelle exception que vous passez à l'appelant. Dans ce cas, la trace de la pile de l'exception d'origine, qui est disponible à partir de la propriété StackTrace, n'est pas conservée.

Expression

À compter de C# 7.0, throw peut être utilisé comme expression et comme instruction. Ainsi, une exception peut être levée dans des contextes qui n'étaient précédemment pas pris en charge. Elles incluent notamment :

  • L'opérateur conditionnel : l'exemple suivant utilise une expression throw pour lever une exception ArgumentException si une méthode reçoit un tableau de chaînes vide. Avant C# 7.0, cette logique devait figurer dans une instruction if/else.

  • L'opérateur de fusion de Null : dans l'exemple suivant, une expression throw est utilisée avec un opérateur de fusion de Null pour lever une exception si la chaîne assignée à une propriété Name est null.

  • Un lambda ou une méthode expression-bodied : l'exemple suivant illustre une méthode expression-bodied qui lève une exception InvalidCastException, car une conversion vers une valeur DateTime n'est pas prise en charge.


try-catch

L'instruction try-catch consiste en un bloc try suivi d'une ou plusieurs clauses catch qui spécifient des gestionnaires pour différentes exceptions.

Notes

Quand une exception est levée, le Common Language Runtime (CLR) recherche l'instruction catch qui gère cette exception. Si la méthode en cours d'exécution ne contient pas un tel bloc catch, le CLR examine la méthode qui a appelé la méthode actuelle, puis remonte la pile des appels. Si aucun bloc catch n'est trouvé, alors le CLR affiche un message d'exception non gérée à l'utilisateur et arrête l'exécution du programme.

Le bloc try contient le code protégé susceptible de provoquer l'exception. Le bloc est exécuté jusqu'à ce qu'une exception soit levée ou qu'il se soit correctement terminé. Par exemple, la tentative suivante d'effectuer un cast d'un objet null déclenche l'exception NullReferenceException :

Bien que la clause catch puisse être utilisée sans arguments pour intercepter tout type d'exception, cette utilisation est déconseillée. En général, vous devez intercepter uniquement les exceptions desquelles vous savez comment récupérer. Par conséquent, vous devez toujours spécifier un argument d'objet dérivé de System.Exception, par exemple :

Il est possible d'utiliser plusieurs clauses catch spécifiques dans la même instruction try-catch. Dans ce cas, l'ordre des clauses catch est important car les clauses catch sont examinées dans l'ordre. Interceptez les exceptions plus spécifiques avant les moins spécifiques. Le compilateur produit une erreur si vous organisez vos blocs catch de sorte qu'un bloc ultérieur ne puisse jamais être atteint.

L'utilisation d'arguments catch constitue un moyen de filtrer les exceptions à gérer. Vous pouvez également utiliser un filtre d'exception qui examine l'exception pour déterminer si elle doit être prise en charge. Si le filtre d'exception retourne la valeur false, la recherche d'un gestionnaire se poursuit.

L'utilisation de filtres d'exceptions est préférable à une interception et une nouvelle levée, car les filtres laissent la pile intact. Si un gestionnaire ultérieur vide la pile, vous pouvez déterminer d'où l'exception provient à l'origine, au lieu de déterminer simplement le dernier emplacement auquel elle a été levée. Une utilisation courante des expressions de filtre d'exception est liée à la journalisation. Vous pouvez créer un filtre qui retourne toujours false et dont la sortie est journalisée. Vous pouvez journaliser les exceptions au fur et à mesure sans avoir à les prendre en charge et à les lever de nouveau.

Une instruction throw peut être utilisée dans un bloc catch pour lever une nouvelle fois l'exception interceptée par l'instruction catch. L'exemple suivant extrait des informations sources d'une exception IOException, puis lève l'exception à la méthode parente.

Vous pouvez intercepter une seule exception et lever une exception différente. Dans ce cas, spécifiez l'exception que vous interceptez en tant qu'exception interne, comme illustré dans l'exemple suivant.

Vous pouvez également lever à nouveau une exception quand une condition spécifiée a la valeur true, comme illustré dans l'exemple suivant.


Il est également possible d'utiliser un filtre d'exception pour obtenir un résultat similaire d'une manière souvent plus claire (sans modifier la pile, comme expliqué précédemment). L'exemple suivant a un comportement similaire à l'exemple précédent pour les appelants. La fonction lève et retourne InvalidCastException à l'appelant quand e.Data a une valeur null.

Dans un bloc try, initialisez uniquement les variables qui y sont déclarées. Sinon, une exception peut se produire avant la fin de l'exécution du bloc. Par exemple, dans l'exemple de code suivant, la variable n est initialisée à l'intérieur du bloc try. Une tentative d'utilisation de cette variable en dehors du bloc try dans l'instruction Write(n) génère une erreur du compilateur.

Exceptions dans les méthodes async

Une méthode async est marquée par un modificateur async et contient généralement une ou plusieurs expressions ou instructions await. Une expression await applique l'opérateur await à un Task ou Task‹TResult›.

Quand le contrôle atteint un await dans la méthode async, la progression de la méthode est interrompue jusqu'à ce que la tâche attendue se termine. Quand la tâche est terminée, l'exécution peut reprendre dans la méthode.
La tâche terminée à laquelle await est appliqué peut être dans un état d'erreur en raison d'une exception non gérée dans la méthode qui retourne la tâche. L'attente de la tâche lève une exception. Une tâche peut également se terminer dans un état annulé si le processus asynchrone qui la retourne est annulé. L'attente d'une tâche annulée lève une OperationCanceledException.
Pour intercepter l'exception, attendez la tâche dans un bloc try, puis interceptez l'exception dans le bloc catch associé.

Une tâche peut être dans un état d'erreur car plusieurs exceptions se sont produites dans la méthode async attendue. Par exemple, la tâche peut être le résultat d'un appel à Task.WhenAll. Quand vous attendez une telle tâche, une seule des exceptions est interceptée et vous ne pouvez pas prévoir laquelle.

Exemple

Dans l'exemple suivant, le bloc try contient un appel à la méthode ProcessString qui risque de provoquer une exception. La clause catch contient le gestionnaire d'exceptions qui affiche simplement un message à l'écran. Quand l'instruction throw est appelée depuis MyMethod, le système recherche l'instruction catch et affiche le message Exception caught.

Dans l'exemple suivant, deux blocs catch sont utilisés, et l'exception la plus spécifique, qui apparaît la première, est interceptée.

Pour intercepter l'exception la moins spécifique, vous pouvez remplacer l'instruction throw dans ProcessString par l'instruction suivante : throw new Exception().

Si vous placez le bloc catch le moins spécifique en premier dans l'exemple, le message d'erreur suivant s'affiche : A previous catch clause already catches all exceptions of this or a super type ('System.Exception').

L'exemple suivant illustre la gestion des exceptions pour les méthodes async. Pour intercepter une exception levée par une tâche async, placez l'expression await dans un bloc try et interceptez-la dans un bloc catch.

Supprimez les marques de commentaire de la ligne throw new Exception dans l'exemple pour illustrer la gestion des exceptions. La propriété IsFaulted de la tâche a la valeur True, la propriété Exception.InnerException de la tâche a la valeur de l'exception et l'exception est interceptée dans le bloc catch.

Supprimez les marques de commentaire de la ligne throw new OperationCanceledException pour montrer ce qui se passe quand vous annulez un processus asynchrone. La propriété IsCanceled de la tâche a la valeur true et l'exception est interceptée dans le bloc catch. Sous certaines conditions qui s'appliquent à cet exemple, la propriété IsFaulted de la tâche a la valeur true et IsCanceled a la valeur false.

L'exemple suivant illustre la gestion des exceptions quand plusieurs tâches peuvent entraîner plusieurs exceptions. Le bloc try attend la tâche retournée par un appel à Task.WhenAll. La tâche est terminée quand les trois tâches auxquelles WhenAll est appliqué sont terminées.

Chacune de ces trois tâches provoque une exception. Le bloc catch itère au sein des exceptions, qui sont trouvent dans la propriété Exception.InnerExceptions de la tâche retournée par Task.WhenAll.


try-finally

En utilisant un bloc finally, vous pouvez nettoyer toutes les ressources allouées dans un bloc try et vous pouvez exécuter du code même si une exception se produit dans le bloc try. En règle générale, les instructions d'un bloc finally s'exécutent lorsque le contrôle quitte une instruction try. Le transfert de contrôle peut se produire suite à une exécution normale, à l'exécution d'une instruction break, continue, goto ou return, ou à la propagation d'une exception hors de l'instruction try.

Dans une exception gérée, le bloc finally associé est assuré d'être exécuté. Toutefois, si l'exception n'est pas gérée, l'exécution du bloc finally dépend de la manière dont l'opération de déroulement d'exception est déclenchée. Ceci, à son tour, dépend du paramétrage de votre ordinateur.

En général, lorsqu'une exception non gérée met fin à une application, que le bloc finally soit exécuté ou non n'est pas important. Toutefois, si vous avez des instructions dans un bloc finally qui doivent être exécutées même dans cette situation, une solution consiste à ajouter un bloc catch à l'instruction try-finally. Ou bien, vous pouvez intercepter l'exception qui peut être levée dans le bloc try d'une instruction try-finally plus haut dans la pile des appels. Autrement dit, vous pouvez intercepter l'exception dans la méthode qui appelle la méthode contenant l'instruction try-finally, ou dans la méthode qui appelle cette méthode, ou dans n'importe quelle méthode figurant dans la pile des appels. Si l'exception n'est pas interceptée, l'exécution du bloc finally varie selon que le système d'exploitation choisit de déclencher une opération de déroulement d'exception.

Exemple

Dans l'exemple suivant, une instruction de conversion non valide provoque une exception System.InvalidCastException. L'exception n'est pas gérée.

Dans l'exemple suivant, une exception issue de la méthode TryCast est interceptée dans une méthode plus loin dans la pile des appels.

Pour plus d'informations sur finally, consultez try-catch-finally.

try-catch-finally

catch et finally sont souvent utilisés ensemble pour obtenir et utiliser des ressources dans un bloc try, pour traiter des circonstances exceptionnelles dans un bloc catch et pour libérer les ressources dans le bloc finally.

Pour plus d'informations et des exemples sur la génération répétée d'exceptions, consultez try-catch et Génération d'exceptions. Pour plus d'informations sur le bloc finally, consultez try-finally.

Exemple