Instructions de gestion des exceptions : throw, try-catch, try-finally et try-catch-finally

Vous utilisez les instructions throw et try pour travailler avec des exceptions. Utilisez l’instruction throw pour lever une exception. Utilisez l’instruction try pour intercepter et gérer les exceptions qui peuvent se produire pendant l’exécution d’un bloc de code.

Instruction throw

L’instruction throw lève une exception :

if (shapeAmount <= 0)
{
    throw new ArgumentOutOfRangeException(nameof(shapeAmount), "Amount of shapes must be positive.");
}

Dans une instruction throw e;, le résultat de l’expression e doit être implicitement convertible en System.Exception.

Vous pouvez utiliser les classes d’exception intégrées, par exemple ArgumentOutOfRangeException ou InvalidOperationException. .NET fournit également les méthodes d’assistance suivantes pour lever des exceptions dans certaines conditions : ArgumentNullException.ThrowIfNull et ArgumentException.ThrowIfNullOrEmpty. Vous pouvez également définir vos propres classes d’exception qui dérivent de System.Exception. Pour plus d’informations, consultez Création et levée d’exceptions.

À l’intérieur d’un bloc catch, vous pouvez utiliser une instruction throw; pour lever à nouveau l’exception gérée par le bloc catch :

try
{
    ProcessShapes(shapeAmount);
}
catch (Exception e)
{
    LogError(e, "Shape processing failed.");
    throw;
}

Notes

throw; conserve la trace de pile d’origine de l’exception, qui est stockée dans la propriété Exception.StackTrace. À l’inverse, throw e; met à jour la propriété StackTrace de e.

Quand une exception est levée, le Common Language Runtime (CLR) recherche le bloc catch qui gère cette exception. Si la méthode exécutée ne contient pas un tel bloc catch, le CLR examine la méthode qui a appelé la méthode actuelle, puis remonte la pile des appels. Si aucun bloc catch n’est trouvé, le CLR met fin au thread en cours d’exécution. Pour plus d’informations, consultez la section Comment les exceptions sont gérées de la spécification du langage C#.

Expression throw

Vous pouvez également utiliser throw comme expression. Cela peut être pratique dans un certain nombre de cas, notamment :

  • l’opérateur conditionnel : L’exemple suivant utilise une expression throw pour lever un ArgumentException lorsque le tableau args passé est vide :

    string first = args.Length >= 1 
        ? args[0]
        : throw new ArgumentException("Please supply at least one argument.");
    
  • l’opérateur de fusion de Null : L’exemple suivant utilise une expression throw pour lever un ArgumentNullException lorsque la chaîne à affecter à une propriété est null :

    public string Name
    {
        get => name;
        set => name = value ??
            throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null");
    }
    
  • un lambda ou une méthode expression-bodied : L’exemple suivant utilise une expression throw pour lever un InvalidCastException pour indiquer qu’une conversion en valeur DateTime n’est pas prise en charge :

    DateTime ToDateTime(IFormatProvider provider) =>
             throw new InvalidCastException("Conversion to a DateTime is not supported.");
    

Instruction try

Vous pouvez utiliser l’instruction try sous l’une des formes suivantes : try-catch - pour gérer les exceptions qui peuvent se produire pendant l’exécution du code à l’intérieur d’un bloc try, try-finally - pour spécifier le code qui est exécuté lorsque le contrôle quitte le bloc try et try-catch-finally - en combinaison des deux formulaires précédents.

Instruction try-catch

Utilisez l’instruction try-catch pour intercepter et gérer les exceptions qui peuvent se produire pendant l’exécution d’un bloc de code. Placez le code là où une exception peut se produire à l’intérieur d’un bloc try. Utilisez une clause catch pour spécifier le type de base des exceptions que vous souhaitez gérer dans le bloc catch correspondant :

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}

Vous pouvez fournir plusieurs clauses catch :

try
{
    var result = await ProcessAsync(-3, 4, cancellationToken);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}
catch (OperationCanceledException)
{
    Console.WriteLine("Processing is cancelled.");
}

Lorsqu’une exception se produit, les clauses catch sont examinées dans l’ordre spécifié, de haut en bas. Au maximum, un seul bloc catch est exécuté pour toute exception levée. Comme le montre également l’exemple précédent, vous pouvez omettre la déclaration d’une variable d’exception et spécifier uniquement le type d’exception dans une clause catch. Une clause catch sans type d’exception spécifié correspond à n’importe quelle exception et, le cas échéant, doit être la dernière clause catch.

Si vous souhaitez lever à nouveau une exception interceptée, utilisez l’instruction throw, comme dans l’exemple suivant :

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e)
{
    LogError(e, "Processing failed.");
    throw;
}

Notes

throw; conserve la trace de pile d’origine de l’exception, qui est stockée dans la propriété Exception.StackTrace. À l’inverse, throw e; met à jour la propriété StackTrace de e.

Un filtre d’exception when

En plus d’un type d’exception, vous pouvez également spécifier un filtre d’exception qui examine plus en détail une exception et détermine si le bloc catch correspondant gère cette exception. Un filtre d’exception est une expression booléenne qui suit le mot clé when, comme dans l’exemple suivant :

try
{
    var result = Process(-3, 4);
    Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e) when (e is ArgumentException || e is DivideByZeroException)
{
    Console.WriteLine($"Processing failed: {e.Message}");
}

L’exemple précédent utilise un filtre d’exception pour fournir un seul bloc catch pour gérer les exceptions de deux types spécifiés.

Vous pouvez fournir plusieurs clauses catch pour le même type d’exception si elles se distinguent par des filtres d’exception. L’une de ces clauses peut ne pas avoir de filtre d’exception. Si une telle clause existe, il doit s’agir de la dernière des clauses qui spécifient ce type d’exception.

Si une clause catch a un filtre d’exception, elle peut spécifier le type d’exception qui est le même ou moins dérivé qu’un type d’exception d’une clause catch qui apparaît après elle. Par exemple, si un filtre d’exception est présent, une clause catch (Exception e) n’a pas besoin d’être la dernière clause.

Exceptions dans les méthodes asynchrones et itérateurs

Si une exception se produit dans une fonction asynchrone, elle se propage à l’appelant de la fonction lorsque vous attendez le résultat de la fonction, comme le montre l’exemple suivant :

public static async Task Run()
{
    try
    {
        Task<int> processing = ProcessAsync(-1);
        Console.WriteLine("Launched processing.");

        int result = await processing;
        Console.WriteLine($"Result: {result}.");
    }
    catch (ArgumentException e)
    {
        Console.WriteLine($"Processing failed: {e.Message}");
    }
    // Output:
    // Launched processing.
    // Processing failed: Input must be non-negative. (Parameter 'input')
}

private static async Task<int> ProcessAsync(int input)
{
    if (input < 0)
    {
        throw new ArgumentOutOfRangeException(nameof(input), "Input must be non-negative.");
    }

    await Task.Delay(500);
    return input;
}

Si une exception se produit dans une méthode d’itérateur, elle se propage à l’appelant uniquement lorsque l’itérateur passe à l’élément suivant.

Instruction try-finally

Dans une instruction try-finally, le bloc finally est exécuté lorsque le contrôle quitte le bloc try. Le contrôle peut quitter le bloc try à la suite de

  • une exécution normale,
  • l’exécution d’une instruction de saut (c’est-à-dire, return, break, continue ou goto), ou
  • la propagation d’une exception hors du bloc try.

L’exemple suivant utilise le bloc finally pour réinitialiser l’état d’un objet avant que le contrôle quitte la méthode :

public async Task HandleRequest(int itemId, CancellationToken ct)
{
    Busy = true;

    try
    {
        await ProcessAsync(itemId, ct);
    }
    finally
    {
        Busy = false;
    }
}

Vous pouvez également utiliser le bloc finally pour nettoyer les ressources allouées utilisées dans le bloc try.

Notes

Lorsque le type d’une ressource implémente l’interface IDisposable ou IAsyncDisposable, considérez l’instruction using. L’instruction using garantit que les ressources acquises sont supprimées lorsque le contrôle quitte l’instruction using. Le compilateur transforme une instruction using en instruction try-finally.

L’exécution du bloc finally varie selon que le système d’exploitation choisit de déclencher une opération de déroulement d’exception. Les seuls cas où les blocs finally ne sont pas exécutés impliquent l’arrêt immédiat d’un programme. Par exemple, un tel arrêt peut se produire en raison de l’appel Environment.FailFast ou d’une exception OverflowException ou InvalidProgramException. La plupart des systèmes d’exploitation effectuent un nettoyage raisonnable des ressources dans le cadre de l’arrêt et du déchargement du processus.

Instruction try-catch-finally

Vous utilisez une instruction try-catch-finally pour gérer les exceptions qui peuvent se produire pendant l’exécution du bloc try et pour spécifier le code qui doit être exécuté lorsque le contrôle quitte l’instruction try :

public async Task ProcessRequest(int itemId, CancellationToken ct)
{
    Busy = true;

    try
    {
        await ProcessAsync(itemId, ct);
    }
    catch (Exception e) when (e is not OperationCanceledException)
    {
        LogError(e, $"Failed to process request for item ID {itemId}.");
        throw;
    }
    finally
    {
        Busy = false;
    }

}

Lorsqu’une exception est gérée par un bloc catch, le bloc finally est exécuté après l’exécution de ce bloc catch (même si une autre exception se produit pendant l’exécution du bloc catch). Pour plus d’informations sur les blocs catch et finally, consultez respectivement les sections L’instruction try-catch et L’instructiontry-finally.

spécification du langage C#

Pour plus d’informations, consultez les sections suivantes de la spécification du langage C# :

Voir aussi