Gestion des erreurs
Gérer les erreurs avec les exceptions
Notions théoriques
Pourquoi les exceptions ?
En C#, une exception est un signal qu'une erreur s'est produite pendant l'exécution. Sans gestion des exceptions, le programme s'arrête brutalement avec un message d'erreur illisible. Avec try/catch, on intercepte l'erreur et on réagit proprement.
// Sans gestion : crash si le fichier n'existe pas
string contenu = File.ReadAllText("notes.txt"); // ← arrêt brutal possible
// Avec gestion : message clair, programme continue
try
{
string contenu = File.ReadAllText("notes.txt");
Console.WriteLine(contenu);
}
catch (FileNotFoundException)
{
Console.WriteLine("Fichier introuvable. Vérifiez le chemin.");
}
Structure try / catch / finally
try
{
// Code susceptible de générer une exception
int[] tableau = new int[5];
tableau[10] = 42; // IndexOutOfRangeException !
}
catch (IndexOutOfRangeException ex)
{
// Exécuté si IndexOutOfRangeException est levée
Console.WriteLine($"Erreur d'index : {ex.Message}");
}
catch (Exception ex)
{
// Intercepte TOUTES les autres exceptions
Console.WriteLine($"Erreur inattendue : {ex.Message}");
}
finally
{
// Exécuté TOUJOURS, qu'il y ait eu une exception ou non
Console.WriteLine("Bloc finally : exécuté dans tous les cas.");
}
catchPlacez toujours les exceptions les plus spécifiques en premier et Exception en dernier. Si vous mettez Exception en premier, elle interceptera tout et les blocs suivants seront inaccessibles (le compilateur signale l'erreur).
Exceptions courantes en C#
| Exception | Déclenchée quand |
|---|---|
ArgumentNullException | Argument null interdit |
ArgumentException | Argument invalide |
ArgumentOutOfRangeException | Argument hors plage autorisée |
InvalidOperationException | Opération impossible dans l'état actuel |
NullReferenceException | Accès à un membre d'un objet null |
IndexOutOfRangeException | Index hors des limites d'un tableau |
FormatException | int.Parse() sur une chaîne non numérique |
OverflowException | Dépassement de capacité (ex: checked { }) |
FileNotFoundException | Fichier introuvable |
IOException | Erreur d'entrée/sortie générique |
DivideByZeroException | Division par zéro (entiers) |
Lever une exception avec throw
static double Diviser(double a, double b)
{
if (b == 0)
throw new ArgumentException("Le diviseur ne peut pas être zéro.", nameof(b));
return a / b;
}
try
{
Console.WriteLine(Diviser(10, 0));
}
catch (ArgumentException ex)
{
Console.WriteLine($"Paramètre invalide : {ex.Message}");
}
nameof(b) — le nom du paramètre sans risque de fautenameof(b) retourne la chaîne "b". Si vous renommez le paramètre plus tard, nameof se met à jour automatiquement. C'est préférable à écrire "b" en dur.
Créer ses propres exceptions
On hérite de Exception (ou d'une exception existante) pour créer des exceptions métier explicites :
class NoteInvalideException : Exception
{
public double NoteRecue { get; }
public NoteInvalideException(double note)
: base($"La note {note} est invalide (doit être entre 0 et 20).")
{
NoteRecue = note;
}
}
// Utilisation
static void ValiderNote(double note)
{
if (note < 0 || note > 20)
throw new NoteInvalideException(note);
}
try
{
ValiderNote(25.5);
}
catch (NoteInvalideException ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine($"Valeur reçue : {ex.NoteRecue}");
}
Le filtre when
when permet d'affiner une clause catch avec une condition :
try
{
// Appel réseau simulé
throw new IOException("Connexion refusée");
}
catch (IOException ex) when (ex.Message.Contains("Connexion"))
{
Console.WriteLine("Problème réseau — réessayez dans quelques secondes.");
}
catch (IOException ex)
{
Console.WriteLine($"Erreur I/O : {ex.Message}");
}
throw; vs throw ex; — préserver la pile d'appels
try
{
File.ReadAllText("données.txt");
}
catch (IOException ex)
{
Console.Error.WriteLine($"Journalisé : {ex.Message}");
throw; // BON : relance l'exception originale avec la pile d'appels intacte
// throw ex; // MAUVAIS : crée une nouvelle exception et perd la pile d'appels originale
}
En Java, certaines méthodes obligent à déclarer les exceptions qu'elles peuvent lever (throws IOException). En C#, aucune exception n'est vérifiée : le compilateur ne vous oblige jamais à entourer un appel de méthode d'un try/catch. C'est vous qui décidez quoi gérer. En contrepartie, une exception non interceptée fait planter l'application.
Exemple pratique
// Calculatrice robuste avec gestion des erreurs
static double Calculer(string operateur, double a, double b)
{
return operateur switch
{
"+" => a + b,
"-" => a - b,
"*" => a * b,
"/" when b == 0 => throw new DivideByZeroException("Division par zéro impossible."),
"/" => a / b,
_ => throw new ArgumentException($"Opérateur inconnu : '{operateur}'", nameof(operateur)),
};
}
string[] tests = ["+", "-", "*", "/", "/", "%"];
double[] a_vals = [10, 7, 3, 15, 9, 4];
double[] b_vals = [3, 2, 8, 4, 0, 2];
for (int i = 0; i < tests.Length; i++)
{
try
{
double resultat = Calculer(tests[i], a_vals[i], b_vals[i]);
Console.WriteLine($"{a_vals[i]} {tests[i]} {b_vals[i]} = {resultat}");
}
catch (DivideByZeroException ex)
{
Console.WriteLine($"[DIVISION] {ex.Message}");
}
catch (ArgumentException ex)
{
Console.WriteLine($"[ARGUMENT] {ex.Message}");
}
}
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Vous allez créer un système de validation de bulletins scolaires avec gestion robuste des erreurs.
Étape 1 — Créer une exception personnalisée
Créez une exception métier explicite pour les notes invalides.
Créer ses propres exceptions (NoteInvalideException, ProduitIntrouvableException...) rend le code plus lisible et permet au code appelant de traiter chaque type d'erreur différemment. Évitez de lever Exception directement — c'est trop vague.
Étape 2 — Valider et calculer la moyenne
Levez l'exception si une note est invalide, puis attrapez-la proprement.
Vérifiez les arguments au début de chaque méthode publique. Une erreur détectée tôt (à l'entrée) produit un message d'erreur précis. Une erreur non détectée peut provoquer un comportement inattendu bien plus loin dans le code, difficile à diagnostiquer.
Étape 3 — Gérer les exceptions avec try/catch
Attrapez les différentes exceptions avec des messages adaptés.
L'ordre des blocs catch est crucial. NoteInvalideException doit précéder ArgumentException (dont elle hérite peut-être indirectement), qui doit précéder Exception. Si vous inversez l'ordre, le bloc général intercepte tout et les blocs spécifiques ne s'exécutent jamais — le compilateur signale d'ailleurs cette erreur.