Gestion des Exceptions
Introduction
Dans le monde réel, tout ne se passe pas toujours comme prévu. Un fichier peut être introuvable, une division par zéro peut se produire, ou une conversion de chaîne peut échouer. En programmation, ces situations exceptionnelles sont appelées exceptions. La gestion des exceptions est un mécanisme fondamental qui permet à vos programmes de réagir intelligemment aux erreurs plutôt que de planter brutalement.
Qu'est-ce qu'une exception ?
Une exception est un événement qui interrompt le flux normal d'exécution d'un programme. Quand une exception se produit, le système "lance" (throw) une exception qui doit être "attrapée" (catch) pour éviter que le programme ne s'arrête.
Analogie : Imaginez que vous conduisez sur une route normale. Soudain, il y a un accident qui bloque la route (exception). Vous avez deux choix :
- Sans gestion d'exception : Vous foncez dans l'obstacle et votre voyage s'arrête brutalement
- Avec gestion d'exception : Vous prenez un itinéraire de contournement prévu à l'avance
Pourquoi gérer les exceptions ?
1. Robustesse : Votre programme continue de fonctionner même en cas d'erreur 2. Expérience utilisateur : Messages d'erreur clairs plutôt que des plantages 3. Débogage : Informations précises sur ce qui s'est mal passé 4. Maintenance : Code plus facile à maintenir et déboguer
Types d'exceptions courantes
C# définit de nombreux types d'exceptions pour différentes situations :
Exceptions liées aux données
FormatException: Conversion impossible (ex: "abc" → int)OverflowException: Dépassement de capacité numériqueArgumentException: Argument invalide passé à une méthodeArgumentNullException: Argument null non autorisé
Exceptions liées aux ressources
FileNotFoundException: Fichier introuvableDirectoryNotFoundException: Dossier introuvableUnauthorizedAccessException: Accès refuséOutOfMemoryException: Mémoire insuffisante
Exceptions logiques
DivideByZeroException: Division par zéroIndexOutOfRangeException: Index hors limites d'un tableauNullReferenceException: Tentative d'utilisation d'un objet null
Exception générale
Exception: Classe de base pour toutes les exceptions
La structure try-catch
La gestion des exceptions utilise la structure try-catch qui permet de "tenter" une opération et de "capturer" les erreurs :
Syntaxe de base
try
{
// Code qui peut lever une exception
int resultat = int.Parse("abc");
}
catch (FormatException)
{
// Code exécuté si une FormatException se produit
Console.WriteLine("Erreur : format invalide !");
}Exemple pratique : Division sécurisée
using System;
class Program
{
static void Main()
{
Console.Write("Entrez le premier nombre : ");
string input1 = Console.ReadLine();
Console.Write("Entrez le second nombre : ");
string input2 = Console.ReadLine();
try
{
double nombre1 = double.Parse(input1);
double nombre2 = double.Parse(input2);
if (nombre2 == 0)
throw new DivideByZeroException("Division par zéro détectée !");
double resultat = nombre1 / nombre2;
Console.WriteLine($"Résultat : {nombre1} / {nombre2} = {resultat}");
}
catch (FormatException)
{
Console.WriteLine("Erreur : Veuillez entrer des nombres valides.");
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"Erreur : {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Erreur inattendue : {ex.Message}");
}
}
}Multiples blocs catch
Vous pouvez avoir plusieurs blocs catch pour gérer différents types d'exceptions :
Ordre d'importance
⚠️ Important : Les blocs catch sont évalués de haut en bas. Placez toujours les exceptions les plus spécifiques en premier et la plus générale (Exception) en dernier.
try
{
int[] nombres = { 1, 2, 3 };
string input = Console.ReadLine();
int index = int.Parse(input);
Console.WriteLine(nombres[index]);
}
catch (FormatException)
{
Console.WriteLine("Erreur : L'index doit être un nombre entier.");
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Erreur : Index hors limites du tableau.");
}
catch (Exception ex)
{
Console.WriteLine($"Erreur générale : {ex.Message}");
}Récupérer les détails de l'exception
try
{
// Code risqué
int resultat = int.Parse("invalid");
}
catch (FormatException ex)
{
Console.WriteLine($"Message : {ex.Message}");
Console.WriteLine($"Type : {ex.GetType().Name}");
Console.WriteLine($"Stack trace : {ex.StackTrace}");
}Le bloc finally
Le bloc finally s'exécute toujours, qu'une exception soit levée ou non. Il est utilisé pour le code de nettoyage :
FileStream fichier = null;
try
{
fichier = new FileStream("data.txt", FileMode.Open);
// Traitement du fichier
}
catch (FileNotFoundException)
{
Console.WriteLine("Fichier non trouvé.");
}
finally
{
// Ce code s'exécute TOUJOURS
if (fichier != null)
{
fichier.Close();
Console.WriteLine("Fichier fermé proprement.");
}
}Ordre d'exécution
try
{
Console.WriteLine("1. Dans try");
throw new Exception("Test");
Console.WriteLine("2. Après exception (jamais exécuté)");
}
catch (Exception)
{
Console.WriteLine("3. Dans catch");
}
finally
{
Console.WriteLine("4. Dans finally (toujours exécuté)");
}
Console.WriteLine("5. Après try-catch-finally");
// Sortie :
// 1. Dans try
// 3. Dans catch
// 4. Dans finally (toujours exécuté)
// 5. Après try-catch-finallyLancer des exceptions avec throw
Vous pouvez créer et lancer vos propres exceptions avec le mot-clé throw :
Lancer une exception standard
static double CalculerRacine(double nombre)
{
if (nombre < 0)
{
throw new ArgumentException("Impossible de calculer la racine d'un nombre négatif.");
}
return Math.Sqrt(nombre);
}Relancer une exception
try
{
// Code qui peut échouer
ProcessData();
}
catch (FileNotFoundException ex)
{
// Enregistrer l'erreur dans un log
LogError(ex);
// Relancer l'exception pour qu'elle soit gérée plus haut
throw; // Préserve la stack trace originale
}Lancer une nouvelle exception avec cause
try
{
int.Parse("invalid");
}
catch (FormatException ex)
{
// Encapsuler dans une exception plus spécifique
throw new ApplicationException("Erreur lors du traitement des données", ex);
}Exemple complet : Calculatrice robuste
using System;
class CalculatriceRobuste
{
static void Main()
{
bool continuer = true;
while (continuer)
{
try
{
Console.WriteLine("\n=== Calculatrice ===");
Console.Write("Premier nombre : ");
double a = LireNombre();
Console.Write("Opération (+, -, *, /) : ");
char operation = LireOperation();
Console.Write("Second nombre : ");
double b = LireNombre();
double resultat = Calculer(a, operation, b);
Console.WriteLine($"Résultat : {a} {operation} {b} = {resultat}");
}
catch (FormatException)
{
Console.WriteLine("❌ Erreur : Format de nombre invalide.");
}
catch (DivideByZeroException)
{
Console.WriteLine("❌ Erreur : Division par zéro impossible.");
}
catch (ArgumentException ex)
{
Console.WriteLine($"❌ Erreur : {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"❌ Erreur inattendue : {ex.Message}");
}
finally
{
Console.Write("Continuer ? (o/n) : ");
string reponse = Console.ReadLine();
continuer = reponse?.ToLower() == "o";
}
}
Console.WriteLine("Au revoir !");
}
static double LireNombre()
{
string input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input))
throw new FormatException("Entrée vide.");
return double.Parse(input);
}
static char LireOperation()
{
string input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Length != 1)
throw new ArgumentException("Opération invalide.");
char op = input[0];
if (op != '+' && op != '-' && op != '*' && op != '/')
throw new ArgumentException($"Opération '{op}' non supportée.");
return op;
}
static double Calculer(double a, char operation, double b)
{
switch (operation)
{
case '+': return a + b;
case '-': return a - b;
case '*': return a * b;
case '/':
if (b == 0)
throw new DivideByZeroException();
return a / b;
default:
throw new ArgumentException($"Opération non supportée : {operation}");
}
}
}Bonnes pratiques
1. Ne pas ignorer les exceptions
// ❌ Mauvais : Ignorer silencieusement les erreurs
try
{
int.Parse("invalid");
}
catch
{
// Ne rien faire - DANGEREUX !
}
// ✅ Bon : Au minimum, enregistrer l'erreur
try
{
int.Parse("invalid");
}
catch (Exception ex)
{
Console.WriteLine($"Erreur capturée : {ex.Message}");
// Ou logger dans un fichier
}2. Être spécifique avec les exceptions
// ❌ Trop général
try
{
// Code complexe
}
catch (Exception ex)
{
// Gère tout pareil
}
// ✅ Spécifique
try
{
// Code complexe
}
catch (FileNotFoundException)
{
// Gestion spécifique pour fichier manquant
}
catch (UnauthorizedAccessException)
{
// Gestion spécifique pour accès refusé
}
catch (Exception ex)
{
// Gestion générale en dernier recours
}3. Messages d'erreur informatifs
// ❌ Message peu utile
throw new Exception("Erreur");
// ✅ Message descriptif
throw new ArgumentException($"La valeur {valeur} n'est pas valide. Doit être entre 1 et 100.");4. Ne pas utiliser les exceptions pour le contrôle de flux
// ❌ Mauvais : Utiliser les exceptions pour la logique normale
try
{
int resultat = int.Parse(input);
return resultat;
}
catch
{
return 0; // Valeur par défaut
}
// ✅ Bon : Utiliser TryParse pour éviter l'exception
if (int.TryParse(input, out int resultat))
{
return resultat;
}
return 0; // Valeur par défautExceptions personnalisées
Pour des besoins spécifiques, vous pouvez créer vos propres types d'exceptions :
// Définir une exception personnalisée
public class AgeInvalideException : Exception
{
public int Age { get; }
public AgeInvalideException(int age)
: base($"L'âge {age} n'est pas valide. Doit être entre 0 et 150.")
{
Age = age;
}
public AgeInvalideException(int age, Exception innerException)
: base($"L'âge {age} n'est pas valide.", innerException)
{
Age = age;
}
}
// Utilisation
static void ValiderAge(int age)
{
if (age < 0 || age > 150)
{
throw new AgeInvalideException(age);
}
}
// Dans le code appelant
try
{
ValiderAge(-5);
}
catch (AgeInvalideException ex)
{
Console.WriteLine($"Erreur d'âge : {ex.Message}");
Console.WriteLine($"Âge fourni : {ex.Age}");
}Debugging avec les exceptions
Informations utiles d'une exception
try
{
// Code qui lève une exception
MethodeQuiEchoue();
}
catch (Exception ex)
{
Console.WriteLine($"Type : {ex.GetType().Name}");
Console.WriteLine($"Message : {ex.Message}");
Console.WriteLine($"Source : {ex.Source}");
Console.WriteLine($"Stack Trace :");
Console.WriteLine(ex.StackTrace);
// Si c'est une exception encapsulée
if (ex.InnerException != null)
{
Console.WriteLine($"Exception interne : {ex.InnerException.Message}");
}
}Exemple de stack trace
System.FormatException: Input string was not in a correct format.
at System.Number.ThrowOverflowOrFormatException(ParsingStatus status, TypeCode type)
at System.Number.ParseInt32(ReadOnlySpan`1 value, NumberStyles styles, NumberFormatInfo info)
at System.Int32.Parse(String s)
at Program.Main() in C:\temp\Program.cs:line 12Lecture : L'erreur s'est produite ligne 12 dans Main(), qui a appelé Int32.Parse, etc.
Récapitulatif
Structure fondamentale
try
{
// Code qui peut lever une exception
}
catch (TypeException1 ex)
{
// Gestion spécifique
}
catch (TypeException2)
{
// Gestion sans récupération des détails
}
catch (Exception ex)
{
// Gestion générale (toujours en dernier)
}
finally
{
// Code de nettoyage (optionnel, toujours exécuté)
}Points clés à retenir
✅ Utilisez try-catch pour le code qui peut échouer ✅ Exceptions spécifiques avant les générales ✅ Messages informatifs pour aider le débogage ✅ Finally pour le nettoyage des ressources ✅ Throw pour signaler vos propres erreurs ✅ Ne pas ignorer les exceptions capturées
Quand utiliser les exceptions
✅ Utilisez pour :
- Erreurs exceptionnelles (fichier manquant, réseau coupé)
- Validation d'arguments de méthodes
- Ressources indisponibles
❌ N'utilisez pas pour :
- Le flux de contrôle normal
- Les validations d'entrée utilisateur courantes
- Les performances critiques
La gestion des exceptions est un art qui s'améliore avec l'expérience. Elle rend vos programmes plus robustes et professionnels ! 🛡️
