Simplifier la gestion des exceptions avec les filtres d'exception
Introduction
Depuis la version 6 du langage C#, il est possible d'ajouter une clause when à un bloc catch d'un gestionnaire d'exception. Cette fonctionnalité, appelée filtre d'exception, permet de capturer une exception uniquement lorsqu'une condition est satisfaite, au lieu de capturer systématiquement une exception puis d'effectuer un test à l'intérieur du bloc catch.
Cette approche rend le code plus lisible, évite des traitements inutiles et permet de gérer plus précisément certaines erreurs.
Problématique
Prenons l'exemple d'une méthode qui lit un fichier. Sans filtre d'exception, il est fréquent d'écrire :
int FileLockedError = -2147024864;
try
{
File.ReadAllText(fileName);
}
catch (IOException ex)
{
if (ex.HResult == FileLockedError)
Console.WriteLine("Le fichier est verrouillé.");
else
throw;
}
L’instruction throw; relance l’exception interceptée tout en conservant la Stack Trace d’origine. Elle permet ainsi d’identifier précisément l’instruction qui a provoqué l’erreur. À l’inverse, l’instruction throw ex; relance l’exception en réinitialisant la Stack Trace à partir du bloc catch. Cette dernière masque donc une partie de l’origine de l'exception et rend son diagnostic plus difficile.
Le bloc catch intercepte toutes les exceptions de type IOException, alors que seule une situation particulière nous intéresse : erreur est-elle survenue car le fichier est verrouillé, parce qu'un autre processus l'utilise déjà ?
Le code doit ensuite déterminer s'il s'agit réellement du cas attendu, puis relancer l'exception dans tous les autres cas.
Utilisation du filtre d'exception
Le filtre d'exception implémenté avec le mot-clé when permet d'exprimer directement cette condition.
try
{
File.ReadAllText(fileName);
}
catch (IOException ex) when (ex.HResult == FileLockedError)
{
Console.WriteLine("Le fichier est verrouillé.");
}
Le bloc catch n'est exécuté que lorsque la condition est vraie. Dans le cas contraire, l'exception poursuit normalement sa recherche d'un autre bloc catch.
Plusieurs traitements selon la même exception
Les filtres d'exception permettent également de traiter différemment une même exception :
try
{
SaveCustomer(customer);
}
catch (SqlException ex) when (ex.Number == 2627)
{
Console.WriteLine("Cette clé existe déjà.");
}
catch (SqlException ex) when (ex.Number == 547)
{
Console.WriteLine("Violation de contrainte de clé étrangère.");
}
catch (SqlException ex)
{
Console.WriteLine("Erreur SQL.");
}
Chaque bloc catch traite un cas bien précis sans avoir à imbriquer plusieurs conditions au sein d'un même bloc catch traitant l'exception de type SqlException.
Les expressions du filtre
La clause when peut implémenter n'importe quelle expression booléenne, et pas uniquement se baser sur les propriétés de l'exception capturée. Voici quelques exemples :
Pourquoi préférer le filtre d'exception ?
L'utilisation du filtre d'exception présente plusieurs avantages :
-
Amélioration de la lisibilité du code.
-
Il n'est plus nécessaire de relancer l'exception (en utilisant le mot clé
throw) lorsqu'elle ne correspond pas au cas attendu. -
La pile d'appels (stack trace) est préservée, car lorsqu'un filtre retourne
false, le bloccatchn'est jamais considéré comme ayant capturé l'exception. Celle-ci poursuit directement sa propagation vers lecatchsuivant.