Aller au contenu principal

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.");
}
Ordre des blocs catch

Placez 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#

ExceptionDéclenchée quand
ArgumentNullExceptionArgument null interdit
ArgumentExceptionArgument invalide
ArgumentOutOfRangeExceptionArgument hors plage autorisée
InvalidOperationExceptionOpération impossible dans l'état actuel
NullReferenceExceptionAccès à un membre d'un objet null
IndexOutOfRangeExceptionIndex hors des limites d'un tableau
FormatExceptionint.Parse() sur une chaîne non numérique
OverflowExceptionDépassement de capacité (ex: checked { })
FileNotFoundExceptionFichier introuvable
IOExceptionErreur d'entrée/sortie générique
DivideByZeroExceptionDivision 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 faute

nameof(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
}
C# n'a pas d'exceptions vérifiées

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


Dans quel ordre doit-on placer les blocs `catch` ?


Quel bloc est toujours exécuté, qu'une exception soit levée ou non ?


Quelle est la différence entre `throw;` et `throw ex;` ?


Quel mot-clé C# permet d'ajouter une condition à un bloc `catch` ?


Quelle exception est levée par `int.Parse("abc")` ?


Quelle est la différence entre C# et Java concernant les exceptions ?


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.


Bonne pratique - Exceptions métier explicites

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.


Bonne pratique - Valider les arguments en entrée de méthode

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.


Bonne pratique - Toujours du plus spécifique au plus général

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.

📌 Une solution