Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
La gestion appropriée des exceptions est essentielle pour la fiabilité des applications. Vous pouvez gérer intentionnellement les exceptions attendues pour empêcher votre application de se bloquer. Toutefois, une application bloquée est plus fiable et diagnostique qu’une application avec un comportement non défini.
Cet article décrit les meilleures pratiques pour la gestion et la création d’exceptions.
Gestion des exceptions
Les meilleures pratiques suivantes concernent la façon dont vous gérez les exceptions :
- Utiliser des blocs try/catch/finally pour se remettre des erreurs ou libérer des ressources
- Gérer les conditions courantes pour éviter les exceptions
- Gérer l’annulation et les exceptions asynchrones
- Concevez les classes afin d'éviter les exceptions
- État de restauration lorsque les méthodes ne sont pas terminées en raison d’exceptions
- Capturer et redéclencher les exceptions correctement
Utilisez les blocs try/catch/finally pour gérer les erreurs ou libérer des ressources.
Pour le code qui peut potentiellement générer une exception et lorsque votre application peut récupérer à partir de cette exception, utilisez try
/catch
des blocs autour du code. Dans les blocs catch
, classez toujours les exceptions du plus dérivé au moins dérivé. (Toutes les exceptions dérivent de la classe Exception. Les exceptions dérivées ne sont pas gérées par une clause catch
, lorsqu'elle est précédée par une clause catch
pour une classe d’exception de base.) Lorsque votre code ne peut pas résoudre une exception, n’interceptez pas cette exception. Activez les méthodes plus loin dans la pile des appels pour récupérer si possible.
Nettoyez les ressources qui sont allouées soit par des instructions using
, soit par des blocs finally
. Préférez utiliser les using
instructions pour nettoyer automatiquement les ressources lorsque des exceptions sont lancées. Utilisez des blocs finally
pour nettoyer les ressources qui n’implémentent pas IDisposable. Le code d’une finally
clause est presque toujours exécuté même lorsque des exceptions sont levées.
Gérer les conditions courantes pour éviter les exceptions
Pour les conditions susceptibles de se produire, mais susceptibles de déclencher une exception, envisagez de les gérer de manière à éviter l’exception. Par exemple, si vous essayez de fermer une connexion déjà fermée, vous obtiendrez un InvalidOperationException
. Vous pouvez éviter cela à l’aide d’une if
instruction pour vérifier l’état de connexion avant de tenter de le fermer.
if (conn.State != ConnectionState.Closed)
{
conn.Close();
}
If conn.State <> ConnectionState.Closed Then
conn.Close()
End IF
Si vous ne vérifiez pas l’état de connexion avant de fermer, vous pouvez intercepter l’exception InvalidOperationException
.
try
{
conn.Close();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.GetType().FullName);
Console.WriteLine(ex.Message);
}
Try
conn.Close()
Catch ex As InvalidOperationException
Console.WriteLine(ex.GetType().FullName)
Console.WriteLine(ex.Message)
End Try
L’approche à choisir dépend de la fréquence à laquelle l’événement doit se produire.
Utilisez la gestion des exceptions si l’événement ne se produit pas souvent, c’est-à-dire si l’événement est vraiment exceptionnel et indique une erreur, telle qu’une fin de fichier inattendue. Lorsque vous utilisez la gestion des exceptions, moins de code est exécuté dans des conditions normales.
Vérifiez les conditions d’erreur dans le code si l’événement se produit régulièrement et peut être considéré comme faisant partie de l’exécution normale. Lorsque vous recherchez des conditions d’erreur courantes, moins de code est exécuté, car vous évitez les exceptions.
Remarque
Les vérifications frontales éliminent la plupart du temps les exceptions. Cependant, il peut exister des conditions de concurrence où la condition protégée change entre la vérification et l’opération, et dans ce cas, vous pourriez encore rencontrer une exception.
Appelez les méthodes Try*
pour éviter les exceptions
Si le coût de performances des exceptions est prohibitif, certaines méthodes de bibliothèque .NET fournissent d’autres formes de gestion des erreurs. Par exemple, Int32.Parse lève une OverflowException si la valeur à analyser est trop grande pour être représentée par Int32. Toutefois, Int32.TryParse ne lève pas cette exception. Au lieu de cela, elle retourne une valeur booléenne et a un out
paramètre qui contient l’entier valide analysé en cas de réussite.
Dictionary<TKey,TValue>.TryGetValue a un comportement similaire pour tenter d’obtenir une valeur à partir d’un dictionnaire.
Gérer l’annulation et les exceptions asynchrones
Il est préférable d’intercepter OperationCanceledException au lieu de TaskCanceledException, qui dérive de OperationCanceledException
, lorsque vous appelez une méthode asynchrone. De nombreuses méthodes asynchrones lèvent une exception OperationCanceledException si l’annulation est demandée. Ces exceptions permettent d’arrêter efficacement l’exécution et de dérouler la pile d’appels dès qu’une demande d’annulation est détectée.
Les méthodes asynchrones stockent les exceptions qui sont levées pendant l’exécution dans la tâche qu’elles retournent. Si une exception est stockée dans la tâche retournée, cette exception sera levée lorsque la tâche sera attendue. Les exceptions d’utilisation, telles que ArgumentException, sont toujours levées de façon synchrone. Pour plus d’informations, consultez Exceptions asynchrones.
Classes de conception afin que les exceptions puissent être évitées
Une classe peut fournir des méthodes ou des propriétés qui vous permettent d’éviter d’effectuer un appel qui déclencherait une exception. Par exemple, la FileStream classe fournit des méthodes qui permettent de déterminer si la fin du fichier a été atteinte. Vous pouvez appeler ces méthodes pour éviter l’exception qui est levée si vous dépassez la fin du fichier pendant la lecture. L’exemple suivant montre comment lire à la fin d’un fichier sans déclencher d’exception :
class FileRead
{
public static void ReadAll(FileStream fileToRead)
{
ArgumentNullException.ThrowIfNull(fileToRead);
int b;
// Set the stream position to the beginning of the file.
fileToRead.Seek(0, SeekOrigin.Begin);
// Read each byte to the end of the file.
for (int i = 0; i < fileToRead.Length; i++)
{
b = fileToRead.ReadByte();
Console.Write(b.ToString());
// Or do something else with the byte.
}
}
}
Class FileRead
Public Sub ReadAll(fileToRead As FileStream)
' This if statement is optional
' as it is very unlikely that
' the stream would ever be null.
If fileToRead Is Nothing Then
Throw New System.ArgumentNullException()
End If
Dim b As Integer
' Set the stream position to the beginning of the file.
fileToRead.Seek(0, SeekOrigin.Begin)
' Read each byte to the end of the file.
For i As Integer = 0 To fileToRead.Length - 1
b = fileToRead.ReadByte()
Console.Write(b.ToString())
' Or do something else with the byte.
Next i
End Sub
End Class
Une autre façon d’éviter les exceptions consiste à retourner null
(ou par défaut) pour les cas d’erreur les plus courants au lieu de lever une exception. Un cas d’erreur courant peut être considéré comme un flux normal de contrôle. En retournant null
(ou par défaut) dans ces cas, vous réduisez l’impact sur les performances d’une application.
Pour les types valeur, déterminez s’il faut utiliser Nullable<T>
ou default
comme indicateur d’erreur pour votre application. En utilisant Nullable<Guid>
, default
devient null
au lieu de Guid.Empty
. Parfois, l’ajout Nullable<T>
peut rendre plus clair lorsqu’une valeur est présente ou absente. D’autres fois, l’ajout Nullable<T>
peut créer des cas supplémentaires pour vérifier qui ne sont pas nécessaires et servent uniquement à créer des sources potentielles d’erreurs.
Restaurer l'état lorsque les méthodes ne se terminent pas en raison d'exceptions
Les appelants doivent être en mesure de supposer qu’il n’existe aucun effet secondaire lorsqu’une exception est levée à partir d’une méthode. Par exemple, si vous avez du code qui transfère de l’argent en le retirant d’un compte pour le déposer dans un autre compte, et qu’une exception est levée pendant l’exécution du dépôt, vous ne souhaitez pas que le retrait demeure valide.
public void TransferFunds(Account from, Account to, decimal amount)
{
from.Withdrawal(amount);
// If the deposit fails, the withdrawal shouldn't remain in effect.
to.Deposit(amount);
}
Public Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)
from.Withdrawal(amount)
' If the deposit fails, the withdrawal shouldn't remain in effect.
[to].Deposit(amount)
End Sub
La méthode précédente ne lève pas d'exception directement. Toutefois, vous devez écrire la méthode afin que le retrait soit inversé si l’opération de dépôt échoue.
Pour gérer cette situation, vous pouvez intercepter toutes les exceptions levées par la transaction de dépôt et annuler le retrait.
private static void TransferFunds(Account from, Account to, decimal amount)
{
string withdrawalTrxID = from.Withdrawal(amount);
try
{
to.Deposit(amount);
}
catch
{
from.RollbackTransaction(withdrawalTrxID);
throw;
}
}
Private Shared Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)
Dim withdrawalTrxID As String = from.Withdrawal(amount)
Try
[to].Deposit(amount)
Catch
from.RollbackTransaction(withdrawalTrxID)
Throw
End Try
End Sub
Cet exemple illustre l’utilisation de throw
pour relancer l’exception d'origine, ce qui permet aux appelants de voir plus facilement la véritable cause du problème sans avoir besoin de consulter la propriété InnerException. Une alternative consiste à lever une nouvelle exception et à inclure l’exception d’origine comme exception interne.
catch (Exception ex)
{
from.RollbackTransaction(withdrawalTrxID);
throw new TransferFundsException("Withdrawal failed.", innerException: ex)
{
From = from,
To = to,
Amount = amount
};
}
Catch ex As Exception
from.RollbackTransaction(withdrawalTrxID)
Throw New TransferFundsException("Withdrawal failed.", innerException:=ex) With
{
.From = from,
.[To] = [to],
.Amount = amount
}
End Try
Capturer et relancer correctement les exceptions
Une fois qu’une exception est levée, une partie des informations qu’elle contient est la trace de la pile. La trace de la pile est une liste de la hiérarchie des appels de méthode qui commence par la méthode qui lève l’exception et se termine par la méthode qui intercepte l’exception. Si vous rétablissez une exception en spécifiant l’exception dans l’instruction throw
, par exemple throw e
, la trace de pile recommence à partir de la méthode actuelle et la liste des appels de méthode entre la méthode d’origine qui a levé l’exception et la méthode actuelle est perdue. Pour conserver les informations du rapport des appels de procédure d’origine avec l’exception, il existe deux options qui dépendent de l’endroit d’où vous relancez l’exception :
- Si vous relancez l'exception depuis le gestionnaire (
catch
bloc) qui a capturé l'exception, utilisez l'instructionthrow
sans spécifier l'exception. La règle d’analyse du code CA2200 vous aide à trouver des emplacements dans votre code où vous risquez de perdre par inadvertance les informations de trace de pile. - Si vous levez à nouveau l’exception à partir d’un autre emplacement que le gestionnaire (bloc
catch
), utilisez ExceptionDispatchInfo.Capture(Exception) pour capturer l’exception dans le gestionnaire et ExceptionDispatchInfo.Throw() quand vous souhaitez la lever à nouveau. Vous pouvez utiliser la ExceptionDispatchInfo.SourceException propriété pour inspecter l’exception capturée.
L’exemple suivant montre comment la ExceptionDispatchInfo classe peut être utilisée et ce que la sortie peut ressembler.
ExceptionDispatchInfo? edi = null;
try
{
var txt = File.ReadAllText(@"C:\temp\file.txt");
}
catch (FileNotFoundException e)
{
edi = ExceptionDispatchInfo.Capture(e);
}
// ...
Console.WriteLine("I was here.");
if (edi is not null)
edi.Throw();
Si le fichier de l’exemple de code n’existe pas, la sortie suivante est générée :
I was here.
Unhandled exception. System.IO.FileNotFoundException: Could not find file 'C:\temp\file.txt'.
File name: 'C:\temp\file.txt'
at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options)
at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize)
at System.IO.File.ReadAllText(String path, Encoding encoding)
at Example.ProcessFile.Main() in C:\repos\ConsoleApp1\Program.cs:line 12
--- End of stack trace from previous location ---
at Example.ProcessFile.Main() in C:\repos\ConsoleApp1\Program.cs:line 24
Lancer des exceptions
Les meilleures pratiques suivantes concernent la façon dont vous lèvez des exceptions :
- Utiliser des types d’exceptions prédéfinis
- Utiliser des méthodes de générateur d’exceptions
- Inclure un message de chaîne localisée
- Utiliser la grammaire appropriée
- Placez correctement les instructions throw
- Ne générez pas d’exceptions dans les clauses finally
- Ne déclenchez pas d’exceptions à partir d’emplacements inattendus
- Lever des exceptions de validation d’argument de manière synchrone
Utiliser des types d’exceptions prédéfinis
Introduisez une nouvelle classe d’exception uniquement lorsqu’une classe d’exception prédéfinie ne s’applique pas. Par exemple:
- Si un jeu de propriétés ou un appel de méthode n’est pas approprié en fonction de l’état actuel de l’objet, lèvez une InvalidOperationException exception.
- Si des paramètres non valides sont passés, lèvez une ArgumentException exception ou l’une des classes prédéfinies qui dérivent de ArgumentException.
Remarque
Bien qu’il soit préférable d’utiliser des types d’exceptions prédéfinis si possible, vous ne devez pas déclencher certains types d’exceptions réservés, tels que AccessViolationException, IndexOutOfRangeExceptionNullReferenceException et StackOverflowException. Pour plus d’informations, consultez CA2201 : Ne déclenchez pas de types d’exceptions réservées.
Utiliser des méthodes de générateur d’exceptions
Il est courant qu’une classe lève la même exception à partir de différents emplacements dans son implémentation. Pour éviter un code excessif, créez une méthode d’assistance qui crée l’exception et la retourne. Par exemple:
class FileReader
{
private readonly string _fileName;
public FileReader(string path)
{
_fileName = path;
}
public byte[] Read(int bytes)
{
byte[] results = FileUtils.ReadFromFile(_fileName, bytes) ?? throw NewFileIOException();
return results;
}
static FileReaderException NewFileIOException()
{
string description = "My NewFileIOException Description";
return new FileReaderException(description);
}
}
Class FileReader
Private fileName As String
Public Sub New(path As String)
fileName = path
End Sub
Public Function Read(bytes As Integer) As Byte()
Dim results() As Byte = FileUtils.ReadFromFile(fileName, bytes)
If results Is Nothing
Throw NewFileIOException()
End If
Return results
End Function
Function NewFileIOException() As FileReaderException
Dim description As String = "My NewFileIOException Description"
Return New FileReaderException(description)
End Function
End Class
Certains types d’exceptions .NET clés ont des méthodes d’assistance statiques similaires throw
qui permettent d’allouer et de lever l’exception. Vous devez appeler ces méthodes au lieu de construire et de lever le type d’exception correspondant :
- ArgumentNullException.ThrowIfNull
- ArgumentException.ThrowIfNullOrEmpty(String, String)
- ArgumentException.ThrowIfNullOrWhiteSpace(String, String)
- ArgumentOutOfRangeException.ThrowIfZero<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfNegative<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfLessThan<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfNotEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfNegativeOrZero<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfGreaterThan<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfLessThanOrEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<T>(T, T, String)
- ObjectDisposedException.ThrowIf
Conseil / Astuce
Les règles d’analyse de code suivantes peuvent vous aider à trouver des emplacements dans votre code où vous pouvez tirer parti de ces helpers statiques throw
: CA1510, CA1511, CA1512 et CA1513.
Si vous implémentez une méthode asynchrone, appelez CancellationToken.ThrowIfCancellationRequested() au lieu de vérifier si l’annulation a été demandée, puis construisez et lèvez OperationCanceledException. Pour plus d’informations, consultez CA2250.
Inclure un message de chaîne localisée
Le message d’erreur que l’utilisateur voit est dérivé de la Exception.Message propriété de l’exception levée, et non du nom de la classe d’exception. En règle générale, vous affectez une valeur à la Exception.Message propriété en passant la chaîne de message à l’argument message
d’un constructeur d’exception.
Pour les applications localisées, vous devez fournir une chaîne de message localisée pour chaque exception que votre application peut lever. Vous utilisez des fichiers de ressources pour fournir des messages d’erreur localisés. Pour plus d’informations sur la localisation des applications et la récupération de chaînes localisées, consultez les articles suivants :
- Guide pratique pour créer des exceptions définies par l’utilisateur avec des messages d’exception localisés
- Les ressources dans les applications .NET
- System.Resources.ResourceManager
Utiliser la grammaire appropriée
Écrivez des phrases claires et incluez la ponctuation de fin. Chaque phrase de la chaîne affectée à la Exception.Message propriété doit se terminer par un point. Par exemple, « La table du journal a débordé » utilise la grammaire et la ponctuation correctes.
Placez correctement les instructions throw
Placez les instructions throw là où le rapport des appels de procédure sera utile. La trace de la pile commence à l'instruction où l'exception est levée et se termine à l'instruction catch
qui intercepte l'exception.
Ne générez pas d’exceptions dans les clauses finally
Ne déclenchez pas d'exceptions dans les clauses finally
. Pour plus d’informations, consultez la règle d’analyse du code CA2219.
Ne déclenchez pas d’exceptions à partir d’emplacements inattendus
Certaines méthodes, telles que Equals
, GetHashCode
et ToString
méthodes, constructeurs statiques et opérateurs d’égalité, ne doivent pas lever d’exceptions. Pour plus d’informations, consultez la règle d’analyse du code CA1065.
Lever des exceptions de validation d’argument de manière synchrone
Dans les méthodes de retour de tâches, vous devez valider les arguments et lever les exceptions correspondantes, telles que ArgumentException et ArgumentNullException, avant d’entrer la partie asynchrone de la méthode. Les exceptions levées dans la partie asynchrone de la méthode sont stockées dans la tâche retournée et n’apparaissent que, par exemple, lorsque la tâche est attendue. Pour plus d’informations, consultez Exceptions dans les méthodes de retour de tâches.
Types d’exceptions personnalisés
Les meilleures pratiques suivantes concernent les types d’exceptions personnalisés :
-
Fin des noms de classes d’exception avec
Exception
- Inclure trois constructeurs
- Fournir des propriétés supplémentaires en fonction des besoins
Fin des noms de classes d’exception avec Exception
Lorsqu’une exception personnalisée est nécessaire, nommez-la de manière appropriée et dérivez-la de la Exception classe. Par exemple:
public class MyFileNotFoundException : Exception
{
}
Public Class MyFileNotFoundException
Inherits Exception
End Class
Inclure trois constructeurs
Utilisez au moins les trois constructeurs courants lors de la création de vos propres classes d’exception : le constructeur sans paramètre, un constructeur qui accepte un message de chaîne et un constructeur qui accepte un message de chaîne et une exception interne.
- Exception(), qui utilise des valeurs par défaut.
- Exception(String), qui accepte un message sous forme de chaîne de caractères.
- Exception(String, Exception), qui accepte un message de chaîne et une exception interne.
Pour obtenir un exemple, consultez Comment : créer des exceptions définies par l’utilisateur.
Fournir des propriétés supplémentaires en fonction des besoins
Fournissez des propriétés supplémentaires pour une exception (en plus de la chaîne de message personnalisée) uniquement lorsqu’il existe un scénario de programmation où les informations supplémentaires sont utiles. Par exemple, le FileNotFoundException fournit la propriété FileName.