Créer et lever des exceptions

Les exceptions sont utilisées pour indiquer qu’une erreur s’est produite pendant l’exécution du programme. Les objets d'exception qui décrivent une erreur sont créés, puis levés avec l'instruction ou l'expression throw. Le runtime recherche ensuite le gestionnaire d’exceptions le plus compatible.

Les programmeurs doivent lever des exceptions quand une ou plusieurs des conditions suivantes sont vérifiées :

  • La méthode ne peut pas remplir sa fonction définie. Par exemple, si un paramètre d’une méthode a une valeur non valide :

    static void CopyObject(SampleClass original)
    {
        _ = original ?? throw new ArgumentException("Parameter cannot be null", nameof(original));
    }
    
  • Un appel inapproprié à un objet est effectué en fonction de l’état de l’objet. Une tentative d’écriture dans un fichier en lecture seule en est un exemple. Dans les cas où l’état d’un objet n’autorise pas une opération, levez une instance d’InvalidOperationException ou un objet basé sur une dérivation de cette classe. Voici un exemple d’une méthode qui lève un objet InvalidOperationException :

    public class ProgramLog
    {
        FileStream logFile = null!;
        public void OpenLog(FileInfo fileName, FileMode mode) { }
    
        public void WriteLog()
        {
            if (!logFile.CanWrite)
            {
                throw new InvalidOperationException("Logfile cannot be read-only");
            }
            // Else write data to the log and return.
        }
    }
    
  • Quand un argument d’une méthode provoque une exception. Dans ce cas, l’exception d’origine doit être interceptée et une instance d’ArgumentException doit être créée. L’exception d’origine doit être passée au constructeur d’ArgumentException comme paramètre InnerException :

    static int GetValueFromArray(int[] array, int index)
    {
        try
        {
            return array[index];
        }
        catch (IndexOutOfRangeException e)
        {
            throw new ArgumentOutOfRangeException(
                "Parameter index is out of range.", e);
        }
    }
    

    Remarque

    L’exemple précédent présente l’utilisation de la propriété InnerException. C’est intentionnellement simplifié. Dans la pratique, vous devez vérifier qu’un index est dans la plage avant de l’utiliser. Vous pouvez utiliser cette technique d’habillage d’une exception lorsqu’un membre d’un paramètre lève une exception que vous n’avez pas pu anticiper avant d’appeler le membre.

Les exceptions contiennent une propriété nommée StackTrace. Cette chaîne contient le nom des méthodes sur la pile des appels actuelle, avec le nom de fichier et le numéro de ligne où l’exception a été levée pour chaque méthode. Un objet StackTrace est créé automatiquement par le CLR (Common Language Runtime) à partir du point de l’instruction throw, de sorte que les exceptions doivent être levées à partir du point où la trace de la pile doit commencer.

Toutes les exceptions contiennent une propriété nommée Message. Cette chaîne doit être définie pour expliquer la raison de l’exception. Les informations sensibles du point de vue de la sécurité ne doivent pas être placées dans le texte du message. Outre Message, ArgumentException contient une propriété nommée ParamName dont la valeur doit être le nom de l’argument qui a provoqué la levée de l’exception. Dans le cas d’une méthode setter de propriété, ParamName doit être défini en tant que value.

Les méthodes public et protected doivent lever des exceptions chaque fois qu’elles ne parviennent pas à remplir leurs fonctions habituelles. La classe d’exception levée est la plus spécifique disponible qui répond aux conditions d’erreur. Ces exceptions doivent être documentées dans le cadre de la fonctionnalité de la classe. De plus, les classes dérivées ou les mises à jour de la classe d’origine doivent conserver le même comportement afin d’assurer la compatibilité descendante.

Pratiques à éviter lors de la levée d’exceptions

La liste suivante identifie les pratiques à éviter lors de la levée d’exceptions :

  • N’utilisez pas d’exceptions pour changer le flux d’un programme dans le cadre d’une exécution ordinaire, mais utilisez-les plutôt pour signaler et gérer les conditions d’erreur.
  • Les exceptions ne doivent pas être retournées comme valeur de retour ou paramètre au lieu d’être levées.
  • Ne levez pas intentionnellement System.Exception, System.SystemException, System.NullReferenceException ni System.IndexOutOfRangeException à partir de votre propre code source.
  • Ne créez pas d’exceptions qui peuvent être levées en mode débogage, mais pas en mode mise en production. Pour identifier des erreurs d’exécution pendant la phase de développement, utilisez plutôt Debug Assert.

Exceptions dans les méthodes de retour de tâches

Les méthodes déclarées avec le modificateur async ont des considérations particulières lorsqu'il s'agit des exceptions. Les exceptions levées dans une méthode async sont stockées dans la tâche retournée et n'apparaissent pas tant que la tâche est attendue, par exemple. Pour plus d'informations sur les exceptions stockées, reportez-vous à Exceptions asynchrones.

Nous vous recommandons de valider les arguments et de lever toutes les exceptions correspondantes, notamment ArgumentException et ArgumentNullException, avant d'entrer les parties asynchrones de vos méthodes. Autrement dit, ces exceptions de validation doivent apparaître de façon synchrone avant le démarrage du travail. L'extrait de code suivant montre un exemple dans lequel les exceptions ArgumentException apparaissent de façon synchrone, tandis que les InvalidOperationException sont stockées dans la tâche retournée si les exceptions sont levées.

// Non-async, task-returning method.
// Within this method (but outside of the local function),
// any thrown exceptions emerge synchronously.
public static Task<Toast> ToastBreadAsync(int slices, int toastTime)
{
    if (slices is < 1 or > 4)
    {
        throw new ArgumentException(
            "You must specify between 1 and 4 slices of bread.",
            nameof(slices));
    }

    if (toastTime < 1)
    {
        throw new ArgumentException(
            "Toast time is too short.", nameof(toastTime));
    }

    return ToastBreadAsyncCore(slices, toastTime);

    // Local async function.
    // Within this function, any thrown exceptions are stored in the task.
    static async Task<Toast> ToastBreadAsyncCore(int slices, int time)
    {
        for (int slice = 0; slice < slices; slice++)
        {
            Console.WriteLine("Putting a slice of bread in the toaster");
        }
        // Start toasting.
        await Task.Delay(time);

        if (time > 2_000)
        {
            throw new InvalidOperationException("The toaster is on fire!");
        }

        Console.WriteLine("Toast is ready!");

        return new Toast();
    }
}

Définir des classes d'exception

Les programmes peuvent lever une classe d’exceptions prédéfinie dans l’espace de noms System (sauf dans les endroits préalablement signalés) ou créer leurs propres classes d’exceptions en les dérivant d’Exception. Les classes dérivées doivent définir au moins trois constructeurs : un constructeur sans paramètre, un qui définit la propriété du message et un qui définit à la fois la propriété Message et la propriété InnerException. Par exemple :

[Serializable]
public class InvalidDepartmentException : Exception
{
    public InvalidDepartmentException() : base() { }
    public InvalidDepartmentException(string message) : base(message) { }
    public InvalidDepartmentException(string message, Exception inner) : base(message, inner) { }
}

Ajoutez de nouvelles propriétés à la classe d’exception lorsque les données qu’elles fournissent sont utiles pour résoudre l’exception. Si de nouvelles propriétés sont ajoutées à la classe d’exceptions dérivée, la méthode ToString() doit être substituée pour retourner les informations ajoutées.

spécification du langage C#

Pour plus d’informations, consultez Exceptions et Instruction throw dans la spécification du langage C#. La spécification du langage est la source de référence pour la syntaxe C# et son utilisation.

Voir aussi