Chargement...

Gestion des exceptions en C# (try, catch, finally)

Apprenez à gérer les exceptions en C# avec les blocs try, catch et finally afin de traiter les erreurs de manière sûre avec exemples.

En C#, la gestion des erreurs permet d’intercepter et de contrôler les situations inattendues pendant l’exécution du programme. Cela évite un arrêt brutal et permet d’afficher un message approprié ou d’exécuter une action alternative. La gestion des erreurs utilise ensemble les blocs try, catch et finally.


Utilisation de try-catch

Le bloc try contient du code pouvant générer une exception. Si une erreur survient, le bloc catch l’intercepte.


try
{
    int nombre = int.Parse("abc"); // Conversion invalide
    Console.WriteLine("Nombre: " + nombre);
}
catch (FormatException ex)
{
    Console.WriteLine("Erreur : format de nombre invalide.");
}
// Sortie :
Erreur : format de nombre invalide.

Plusieurs blocs catch

Il est possible d’utiliser plusieurs catch pour différents types d’erreurs. Chaque type d’exception peut ainsi être traité séparément.


try
{
    int[] nombres = { 1, 2, 3 };
    Console.WriteLine(nombres[5]); // Dépassement d’indice
}
catch (IndexOutOfRangeException)
{
    Console.WriteLine("Erreur : indice de tableau hors limites.");
}
catch (Exception ex)
{
    Console.WriteLine("Erreur inattendue : " + ex.Message);
}

Advanced Error Handling: Exception Filters (when)

En C#, les filtres d’exception permettent d’ajouter une condition à un catch en utilisant le mot-clé when. Cela permet de gérer une exception uniquement si une condition spécifique est remplie.

Le principal avantage des filtres d’exception est que la condition est évaluée avant d’entrer dans le bloc catch. Si la condition retourne false, le runtime ignore ce bloc et continue à rechercher une autre clause catch correspondante.

Cela permet :

Exemple : Gestion des erreurs HTTP selon le code de statut

Dans les applications réelles, les requêtes HTTP retournent souvent différents codes de statut. Grâce aux filtres d’exception, nous pouvons traiter certains codes de manière spécifique tout en gardant un code structuré et lisible.


using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        using var client = new HttpClient();

        try
        {
            HttpResponseMessage response =
                await client.GetAsync("https://api.example.com/users/1");

            response.EnsureSuccessStatusCode();

            string content = await response.Content.ReadAsStringAsync();
            Console.WriteLine(content);
        }
        catch (HttpRequestException ex) 
            when (ex.StatusCode == HttpStatusCode.NotFound)
        {
            Console.WriteLine("Ressource non trouvée (404).");
        }
        catch (HttpRequestException ex) 
            when (ex.StatusCode == HttpStatusCode.Unauthorized)
        {
            Console.WriteLine("Accès non autorisé (401). Vérifiez vos identifiants.");
        }
        catch (HttpRequestException ex)
        {
            Console.WriteLine("Une erreur HTTP générale s’est produite.");
            Console.WriteLine($"Code de statut : {ex.StatusCode}");
        }
    }
}
  

Dans cet exemple :

  • La condition when est évaluée avant d’entrer dans le bloc catch.
  • Si la condition est false, le runtime ignore cette clause catch.
  • Les filtres d’exception ont été introduits avec C# 6 et sont largement utilisés dans le développement backend moderne.

Rethrowing Exceptions: throw; vs throw ex;

Une exception interceptée dans un bloc catch peut être relancée. Cependant, throw; et throw ex; ne se comportent pas de la même manière.

La différence principale réside dans la préservation de la stack trace. La stack trace indique la chaîne d’appels de méthodes qui a conduit à l’erreur ainsi que la ligne exacte où elle s’est produite à l’origine.

Exemple


using System;

class Program
{
    static void Main()
    {
        try
        {
            MethodA();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Erreur enregistrée.");

            // Utilisation correcte :
            throw;

            // Utilisation incorrecte :
            // throw ex;
        }
    }

    static void MethodA()
    {
        MethodB();
    }

    static void MethodB()
    {
        throw new InvalidOperationException("Une erreur s’est produite.");
    }
}
  

Dans cet exemple, si throw; est utilisé, l’exception apparaîtra dans la stack trace comme provenant de MethodB.

Si throw ex; est utilisé à la place, l’exception semblera provenir du bloc catch, et la chaîne d’appels originale sera perdue.

  • Si une exception doit être relancée, il faut toujours privilégier throw;.
  • throw ex; complique le débogage car il altère la stack trace d’origine.
  • Le mot-clé throw peut également être utilisé pour lancer des exceptions personnalisées.

Custom Exceptions

Dans les applications réelles, toutes les erreurs ne doivent pas être représentées par des types d’exception généraux tels que Exception ou d’autres exceptions intégrées. Pour exprimer plus clairement les violations des règles métier (business logic) et les problèmes spécifiques au domaine, il est recommandé de créer des classes d’exception personnalisées.

Cette approche :

Exemple


// 1. Définition des exceptions personnalisées
public class ProjectNotFoundException : Exception
{
    public ProjectNotFoundException(string message) : base(message) { }
}

public class ProjectIsCanceledException : Exception
{
    public int ProjectId { get; }
    public string ProjectName { get; }

    public ProjectIsCanceledException(int projectId, string projectName)
        : base($"Project '{projectName}' (Id: {projectId}) is canceled and cannot be processed.")
    {
        ProjectId = projectId;
        ProjectName = projectName;
    }
}

// 2. Utilisation
public void ProcessProject(Project project)
{
    if (project == null)
    {
        throw new ProjectNotFoundException("Le projet n’est pas enregistré dans le système.");
    }

    if (project.Status == ProjectStatus.Canceled)
    {
        throw new ProjectIsCanceledException(project.Id, project.Name);
    }

    // ...
}
  

Dans cet exemple, une ProjectNotFoundException est levée lorsque le projet n’existe pas dans le système.

Si le projet est annulé, une ProjectIsCanceledException est levée. Cette exception contient non seulement un message d’erreur, mais aussi les valeurs ProjectId et ProjectName, ce qui rend la journalisation et l’analyse des erreurs plus pertinentes.

  • Les exceptions personnalisées représentent généralement des violations de règles métier ou des problèmes liés au domaine.
  • La convention de nommage suit généralement le modèle SomethingException.
  • Des propriétés supplémentaires peuvent être ajoutées afin de fournir davantage d’informations contextuelles.

Bloc finally

Le bloc finally est toujours exécuté, qu’une erreur survienne ou non. Il est souvent utilisé pour libérer des ressources comme fermer des fichiers ou des connexions.


try
{
    Console.WriteLine("Ouverture du fichier...");
    throw new Exception("Fichier introuvable !");
}
catch (Exception ex)
{
    Console.WriteLine("Erreur : " + ex.Message);
}
finally
{
    Console.WriteLine("Fermeture du fichier...");
}
// Sortie :
Ouverture du fichier...
Erreur : Fichier introuvable !
Fermeture du fichier...

Lancer des exceptions personnalisées

Vous pouvez définir vos propres conditions d’erreur et lancer des exceptions avec throw.


static void Diviser(int a, int b)
{
    if (b == 0)
        throw new DivideByZeroException("Erreur : division par zéro !");
    Console.WriteLine("Résultat : " + (a / b));
}

static void Main()
{
    try
    {
        Diviser(10, 0);
    }
    catch (DivideByZeroException ex)
    {
        Console.WriteLine("Erreur : " + ex.Message);
    }
}
// Sortie :
Erreur : division par zéro !

En résumé

  • try : Contient le code susceptible de lever des exceptions.
  • catch : Intercepte et gère les exceptions lorsqu’elles se produisent.
  • finally : S’exécute toujours, généralement pour le nettoyage.
  • Plusieurs blocs catch permettent de gérer différents types d’exceptions séparément.
  • Les filtres d’exception (when) permettent une gestion conditionnelle des exceptions.
  • throw; préserve la stack trace d’origine lors de la relance d’une exception.
  • throw ex; réinitialise la stack trace et doit généralement être évité.
  • Les exceptions personnalisées représentent des erreurs liées au domaine ou aux règles métier.


Articles connexes