Gestion des Exceptions en C#
La gestion des exceptions est un aspect crucial du développement d'applications robustes. La vidéo de Tim Corey intitulée "Gestion des exceptions en C# - Quand les attraper, où les attraper et comment les attraper", fournit une explication détaillée de ce que sont les exceptions, comment les gérer et où les gérer.
Cet article vise à expliquer le traitement des exceptions en C# à l'aide de la vidéo de Tim Corey. Il s'agit d'une fonction puissante qui permet aux développeurs de gérer les erreurs et les conditions exceptionnelles qui surviennent pendant l'exécution du programme. À l'aide des blocs try, catch et finally, C# fournit un moyen structuré de gérer les erreurs d'exécution, de consigner les exceptions et de maintenir le flux du programme.
Introduction
Tim commence par expliquer que de nombreux développeurs ont une vision erronée des exceptions et de leur gestion. Il souligne l'importance de comprendre ce que sont les exceptions, où et comment les gérer correctement pour créer des applications plus robustes.
Construction d'une application console de démonstration
Tim crée une application console dans Visual Studio 2017 pour démontrer la gestion des exceptions. Il recommande d'utiliser des applications de console pour tester de nouveaux sujets, car elles ne nécessitent qu'une configuration minimale et sont faciles à utiliser.
using System;
namespace ExceptionsDemo
{
class Program
{
static void Main(string[] args)
{
// Placeholder for input and output operations
Console.ReadLine();
}
}
}
using System;
namespace ExceptionsDemo
{
class Program
{
static void Main(string[] args)
{
// Placeholder for input and output operations
Console.ReadLine();
}
}
}
Créer une bibliothèque de classes
Tim ajoute une bibliothèque de classes à la solution pour simuler un scénario réel dans lequel différentes méthodes s'appellent les unes les autres.

Il supprime la classe par défaut et crée une nouvelle classe appelée DemoCode.
public class DemoCode
{
// Method to retrieve a number based on the provided position
public int GetNumber(int position)
{
int[] numbers = { 1, 4, 7, 2 };
return numbers[position];
}
// Intermediate method calls GetNumber
public int ParentMethod(int position)
{
return GetNumber(position);
}
// Top-level method calls ParentMethod
public int GrandparentMethod(int position)
{
return ParentMethod(position);
}
}
public class DemoCode
{
// Method to retrieve a number based on the provided position
public int GetNumber(int position)
{
int[] numbers = { 1, 4, 7, 2 };
return numbers[position];
}
// Intermediate method calls GetNumber
public int ParentMethod(int position)
{
return GetNumber(position);
}
// Top-level method calls ParentMethod
public int GrandparentMethod(int position)
{
return ParentMethod(position);
}
}
La classe DemoCode contient des méthodes qui s'appellent les unes les autres, pour finalement récupérer un nombre dans un tableau en fonction de la position donnée.
Simuler une exception
Tim explique que l'application est destinée à démontrer les échecs plutôt que les réussites. Il introduit une exception hors limites en transmettant une position invalide à la méthode GrandparentMethod.
DemoCode demo = new DemoCode();
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
DemoCode demo = new DemoCode();
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
L'exécution du code ci-dessus avec une position non valide entraîne une exception IndexOutOfRange. Tim montre comment le débogueur de Visual Studio met en évidence le problème et fournit des informations détaillées sur l'exception.
Comment NE PAS utiliser try-catch
Tim explique une erreur courante que les développeurs commettent lorsqu'ils découvrent les blocs try-catch. Ils enveloppent souvent tout le bloc de code où ils s'attendent à ce qu'une exception se produise, ce qui peut entraîner une mauvaise gestion.
try
{
int output = 0;
output = numbers[position];
return output;
}
catch (Exception ex)
{
// Avoid returning default values that can mask the problem
return 0;
}
try
{
int output = 0;
output = numbers[position];
return output;
}
catch (Exception ex)
{
// Avoid returning default values that can mask the problem
return 0;
}
Tim souligne que cette approche est problématique parce qu'elle cache l'exception et poursuit l'exécution avec des hypothèses incorrectes. Par exemple, le fait de renvoyer 0 comme valeur par défaut n'est pas forcément approprié et peut entraîner d'autres problèmes.
Gestion correcte des exceptions
Tim souligne que les exceptions fournissent des informations essentielles sur les états inattendus de l'application. Si l'application reste dans cet état sans être traitée correctement, elle peut entraîner d'autres erreurs et une corruption des données.
Au lieu d'avaler les exceptions, il est essentiel de les traiter de manière appropriée. Voici une meilleure approche :
try
{
return numbers[position];
}
catch (Exception ex)
{
// Log the exception or handle it appropriately
Console.WriteLine(ex.Message);
throw; // Re-throw the exception to be handled by a higher-level handler
}
try
{
return numbers[position];
}
catch (Exception ex)
{
// Log the exception or handle it appropriately
Console.WriteLine(ex.Message);
throw; // Re-throw the exception to be handled by a higher-level handler
}
En relançant l'exception, vous vous assurez que le problème est propagé et qu'il peut être traité à un niveau supérieur si nécessaire.
Fournir des informations utiles à l'utilisateur
Tim explique que si certaines exceptions peuvent être gérées de manière élégante sans planter l'application, il est important de fournir un retour d'information utile à l'utilisateur. Par exemple, l'affichage d'une boîte de message ou d'une notification avec l'option de réessayer l'opération.
Plus d'informations utiles : StackTrace
Tim montre comment utiliser la propriété StackTrace de l'objet d'exception pour obtenir des informations détaillées sur l'endroit où l'exception s'est produite. La traduction doit comprendre le numéro de la classe, de la méthode et de la ligne, ce qui est très utile pour le débogage.
try
{
return numbers[position];
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
throw;
}
try
{
return numbers[position];
}
catch (Exception ex)
{
Console.WriteLine(ex.StackTrace);
throw;
}
La propriété StackTrace fournit une trace complète de la pile d'appels, aidant les développeurs à localiser précisément le problème.
Mise en place correcte de try-catch
Tim explique que pour gérer correctement les exceptions, il ne suffit pas de les attraper, mais qu'il faut aussi savoir où placer les blocs try-catch. La clé est de placer les blocs try-catch à un niveau où vous avez suffisamment de contexte pour traiter l'exception de manière appropriée.
Exemple de mauvais placement
Le fait de placer un bloc try-catch au plus profond de la pile d'appels ne permet souvent pas de traiter l'exception de manière efficace, car on ne dispose pas du contexte des opérations de plus haut niveau.
// Deep level exception handling (not ideal)
try
{
return numbers[position];
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
// Deep level exception handling (not ideal)
try
{
return numbers[position];
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw;
}
Exemple de placement correct
Placer le bloc try-catch au niveau supérieur, par exemple dans l'interface utilisateur ou le point d'entrée de l'application, permet de traiter les exceptions dans le contexte complet de l'opération.
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
De cette manière, vous pouvez fournir des messages plus informatifs à l'utilisateur et décider si l'application peut continuer à fonctionner ou si elle doit être arrêtée.
Informations sur la trace de la pile
Tim met l'accent sur l'importance des informations relatives à la trace de la pile pour diagnostiquer les exceptions. La trace de pile fournit un historique détaillé des appels, montrant où l'exception s'est produite et la chaîne d'appels de méthodes qui y a conduit.
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
Ce résultat indique l'emplacement exact de l'exception et le chemin parcouru dans le code, ce qui facilite le débogage et la résolution du problème.
Démonstration de la logique de manipulation
Tim montre comment traiter la logique au niveau approprié. Par exemple, si une méthode est responsable de l'ouverture et de la fermeture d'une connexion à une base de données, elle doit traiter les exceptions pour garantir une gestion correcte des ressources.
public int GrandparentMethod(int position)
{
try
{
Console.WriteLine("Open database connection");
int output = ParentMethod(position);
Console.WriteLine("Close database connection");
return output;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Ensure the exception is propagated
}
}
public int GrandparentMethod(int position)
{
try
{
Console.WriteLine("Open database connection");
int output = ParentMethod(position);
Console.WriteLine("Close database connection");
return output;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Ensure the exception is propagated
}
}
Dans cet exemple, si une exception se produit, la connexion à la base de données n'est pas correctement fermée, ce qui peut entraîner des fuites de ressources. En ajoutant un bloc try-catch, vous pouvez vous assurer que la connexion est fermée même si une exception se produit.
Utilisation du bloc final
Tim présente le bloc finally, qui garantit l'exécution d'un certain code, qu'une exception se produise ou non. Ceci est particulièrement utile pour le nettoyage des ressources, comme la fermeture des connexions aux bases de données.
public int GrandparentMethod(int position)
{
try
{
Console.WriteLine("Open database connection");
int output = ParentMethod(position);
return output;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Re-throw the exception to ensure it's handled by a higher-level handler
}
finally
{
Console.WriteLine("Close database connection");
}
}
public int GrandparentMethod(int position)
{
try
{
Console.WriteLine("Open database connection");
int output = ParentMethod(position);
return output;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Re-throw the exception to ensure it's handled by a higher-level handler
}
finally
{
Console.WriteLine("Close database connection");
}
}
Le bloc finally s'exécute après les blocs try et catch, garantissant la fermeture de la connexion même si une exception est levée.
La déclaration de lancement
Tim explique l'importance de relancer les exceptions pour les faire remonter dans la pile d'appels. Cela permet aux gestionnaires de niveau supérieur de traiter les exceptions de manière appropriée.
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Re-throws the exception to be handled by the calling method
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
throw; // Re-throws the exception to be handled by the calling method
}
Relancer l'exception avec throw ; l'interface utilisateur garantit que l'intégralité de la trace de la pile est préservée, ce qui fournit un contexte précieux pour le débogage.
Surmonter correctement les exceptions
Tim montre comment les exceptions remontent dans la pile d'appels. Chaque méthode vérifie la présence d'un bloc try-catch et traite l'exception ou la transmet à l'appelant.
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
try
{
int result = demo.GrandparentMethod(4); // This will cause an IndexOutOfRangeException
Console.WriteLine($"The value at the given position is {result}");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
Dans cet exemple, la méthode GrandparentMethod attrape l'exception, l'enregistre et la relance. Le bloc try-catch de premier niveau de l'application console traite ensuite l'exception et affiche le message d'erreur et la trace de la pile.
Erreurs courantes dans le traitement des exceptions
Tim met en évidence plusieurs erreurs courantes commises par les développeurs lors de la gestion des exceptions :
-
Utilisant throw ex; :
-
Réécrire la trace de la pile et perdre un contexte précieux.
- Exemple :
catch (Exception ex) { // Incorrect throw ex; // Rewrites stack trace }catch (Exception ex) { // Incorrect throw ex; // Rewrites stack trace }
-
-
Lancer une nouvelle exception :
-
Création d'une nouvelle exception avec un message personnalisé, mais perte de la trace de pile d'origine.
- Exemple :
catch (Exception ex) { // Incorrect throw new Exception("I blew up"); }catch (Exception ex) { // Incorrect throw new Exception("I blew up"); }
-
Créer une nouvelle exception sans perdre la trace de la pile d'origine
Tim explique comment créer une nouvelle exception tout en conservant la trace de la pile d'origine. Cela peut s'avérer utile lorsque vous souhaitez fournir un message d'erreur plus significatif ou un type d'exception différent, tout en conservant le contexte de l'erreur d'origine.
catch (Exception ex)
{
throw new ArgumentException("You passed in bad data", ex);
}
catch (Exception ex)
{
throw new ArgumentException("You passed in bad data", ex);
}
En passant l'exception d'origine (ex) en tant qu'exception interne, vous conservez la trace de la pile d'origine, ce qui est crucial pour le débogage.
Préserver les informations de la trace de la pile
Tim montre comment accéder au message de l'exception originale et à la trace de la pile lors de la création d'une nouvelle exception.
catch (Exception ex)
{
Console.WriteLine("You passed in bad data");
Console.WriteLine(ex.StackTrace);
throw new ArgumentException("You passed in bad data", ex);
}
catch (Exception ex)
{
Console.WriteLine("You passed in bad data");
Console.WriteLine(ex.StackTrace);
throw new ArgumentException("You passed in bad data", ex);
}
Cela permet de s'assurer que l'exception remontée dans la pile contient à la fois le nouveau message et les détails de l'exception d'origine.
Looping à travers les exceptions internes
Tim fournit une méthode pour parcourir en boucle toutes les exceptions internes afin d'en extraire les messages et les traces de pile.
catch (Exception ex)
{
Exception inner = ex;
while (inner != null)
{
Console.WriteLine(inner.StackTrace);
inner = inner.InnerException;
}
throw;
}
catch (Exception ex)
{
Exception inner = ex;
while (inner != null)
{
Console.WriteLine(inner.StackTrace);
inner = inner.InnerException;
}
throw;
}
Cette boucle itère à travers chaque exception interne, en imprimant sa trace de pile, en s'assurant que toutes les couches d'exceptions sont prises en compte.
Gérer différemment les différentes exceptions
Tim explique comment gérer différents types d'exceptions en utilisant plusieurs blocs catch. Cela permet un traitement spécifique en fonction du type d'exception.
try
{
// Code that might throw an exception
}
catch (ArgumentException ex)
{
Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
try
{
// Code that might throw an exception
}
catch (ArgumentException ex)
{
Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
Dans cet exemple, l'exception ArgumentException est gérée spécifiquement par l'impression d'un message personnalisé, tandis que toutes les autres exceptions sont traitées par un gestionnaire général qui imprime le message d'exception et la trace de la pile.

Importance de l'ordre dans les blocs de capture multiples
Tim insiste sur l'importance de l'ordre lors de l'utilisation de plusieurs blocs de capture. Les exceptions les plus spécifiques doivent être traitées en premier, suivies des exceptions plus générales.
try
{
// Code that might throw an exception
}
catch (ArgumentException ex)
{
Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
try
{
// Code that might throw an exception
}
catch (ArgumentException ex)
{
Console.WriteLine("You gave us bad information. Bad user!");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
Si un bloc catch plus général apparaît avant un bloc catch spécifique, il attrapera toutes les exceptions et le bloc catch spécifique ne sera jamais atteint, ce qui entraînera des erreurs de compilation.
Conclusion
Le guide avancé vidéo de Tim Corey sur la gestion des exceptions en C# couvre des techniques essentielles pour créer de nouvelles exceptions, préserver les traces de pile et utiliser efficacement plusieurs blocs catch. En suivant ses meilleures pratiques, les développeurs peuvent créer des applications robustes qui gèrent les exceptions avec élégance et fournissent des informations de débogage précieuses.
