Aanbevolen procedures voor uitzonderingen
De juiste afhandeling van uitzonderingen is essentieel voor de betrouwbaarheid van toepassingen. U kunt opzettelijk verwachte uitzonderingen afhandelen om te voorkomen dat uw app vastloopt. Een vastgelopen app is echter betrouwbaarder en diagnosticeerbaar dan een app met niet-gedefinieerd gedrag.
In dit artikel worden aanbevolen procedures beschreven voor het afhandelen en maken van uitzonderingen.
Afhandeling van uitzonderingen
De volgende aanbevolen procedures zijn van belang voor het afhandelen van uitzonderingen:
- Try/catch/finally blocks gebruiken om te herstellen van fouten of release-resources
- Algemene voorwaarden afhandelen om uitzonderingen te voorkomen
- Annulerings- en asynchrone uitzonderingen vangen
- Ontwerpklassen zodat uitzonderingen kunnen worden vermeden
- Herstelstatus wanneer methoden niet worden voltooid vanwege uitzonderingen
- Uitzonderingen goed vastleggen en opnieuw werpen
Try/catch/finally blocks gebruiken om te herstellen van fouten of release-resources
Gebruik blokken rond de code voor code die mogelijk een uitzondering kan genereren en wanneer uw app deze uitzondering try
/catch
kan herstellen. In catch
blokken kunt u uitzonderingen van de meest afgeleide naar de minst afgeleide altijd orden. (Alle uitzonderingen zijn afgeleid van de Exception klasse. Meer afgeleide uitzonderingen worden niet verwerkt door een catch
component die wordt voorafgegaan door een catch
component voor een basisuitzonderingsklasse.) Wanneer uw code niet kan worden hersteld na een uitzondering, kunt u deze uitzondering niet ondervangen. Schakel methoden in om de aanroepstack zo mogelijk te herstellen.
Resources opschonen die zijn toegewezen met instructies using
of finally
blokken. Geef de voorkeur using
aan instructies om resources automatisch op te schonen wanneer er uitzonderingen worden gegenereerd. Gebruik finally
blokken om resources op te schonen die niet worden geïmplementeerd IDisposable. Code in een finally
component wordt bijna altijd uitgevoerd, zelfs wanneer er uitzonderingen worden gegenereerd.
Algemene voorwaarden afhandelen om uitzonderingen te voorkomen
Voor voorwaarden die waarschijnlijk optreden, maar een uitzondering kan veroorzaken, kunt u overwegen deze op een manier te verwerken die de uitzondering vermijdt. Als u bijvoorbeeld probeert een verbinding te sluiten die al is gesloten, krijgt u een InvalidOperationException
. U kunt dit voorkomen door een if
instructie te gebruiken om de verbindingsstatus te controleren voordat u deze probeert te sluiten.
if (conn.State != ConnectionState.Closed)
{
conn.Close();
}
If conn.State <> ConnectionState.Closed Then
conn.Close()
End IF
Als u de verbindingsstatus niet controleert voordat u afsluit, kunt u de InvalidOperationException
uitzondering ondervangen.
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
De gekozen methode is afhankelijk van hoe vaak u verwacht dat de gebeurtenis plaatsvindt.
Gebruik uitzonderingsafhandeling als de gebeurtenis niet vaak voorkomt, dat wil gezegd, als de gebeurtenis echt uitzonderlijk is en een fout aangeeft, zoals een onverwacht einde van het bestand. Wanneer u uitzonderingsafhandeling gebruikt, wordt minder code uitgevoerd in normale omstandigheden.
Controleer op foutcodes in code als de gebeurtenis regelmatig plaatsvindt en kan worden beschouwd als onderdeel van de normale uitvoering. Wanneer u controleert op veelvoorkomende foutvoorwaarden, wordt minder code uitgevoerd omdat u uitzonderingen vermijdt.
Notitie
Controles vooraf elimineren meestal uitzonderingen. Er kunnen echter racevoorwaarden zijn waarbij de beveiligde voorwaarde verandert tussen de controle en de bewerking, en in dat geval kunt u nog steeds een uitzondering maken.
Methoden aanroepen Try*
om uitzonderingen te voorkomen
Als de prestatiekosten van uitzonderingen verboden zijn, bieden sommige .NET-bibliotheekmethoden alternatieve vormen van foutafhandeling. Genereert bijvoorbeeld Int32.Parse een OverflowException als de waarde die moet worden geparseerd te groot is om te worden weergegeven door Int32. Int32.TryParse Deze uitzondering wordt echter niet gegenereerd. In plaats daarvan retourneert het een Booleaanse waarde en heeft een out
parameter die het geparseerde geldige gehele getal bevat bij succes. Dictionary<TKey,TValue>.TryGetValue heeft vergelijkbaar gedrag voor het ophalen van een waarde uit een woordenlijst.
Annulerings- en asynchrone uitzonderingen vangen
Het is beter om te vangen OperationCanceledException in plaats van , wat is afgeleid van OperationCanceledException
TaskCanceledException, wanneer u een asynchrone methode aanroept. Veel asynchrone methoden genereren een OperationCanceledException uitzondering als annulering wordt aangevraagd. Met deze uitzonderingen kan de uitvoering efficiënt worden gestopt en wordt de callstack niet meer uitgevoerd zodra er een annuleringsaanvraag wordt waargenomen.
Asynchrone methoden slaan uitzonderingen op die worden gegenereerd tijdens de uitvoering in de taak die ze retourneren. Als er een uitzondering wordt opgeslagen in de geretourneerde taak, wordt deze uitzondering gegenereerd wanneer de taak wordt gewacht. Gebruiksonderzondering, zoals ArgumentException, worden nog steeds synchroon gegenereerd. Zie Asynchrone uitzonderingen voor meer informatie.
Ontwerpklassen zodat uitzonderingen kunnen worden vermeden
Een klasse kan methoden of eigenschappen bieden waarmee u kunt voorkomen dat u een aanroep maakt die een uitzondering activeert. De klasse biedt bijvoorbeeld FileStream methoden waarmee u kunt bepalen of het einde van het bestand is bereikt. U kunt deze methoden aanroepen om de uitzondering te voorkomen die wordt gegenereerd als u het einde van het bestand hebt gelezen. In het volgende voorbeeld ziet u hoe u naar het einde van een bestand kunt lezen zonder een uitzondering te activeren:
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
Een andere manier om uitzonderingen te voorkomen, is om te retourneren null
(of standaard) voor de meest voorkomende foutgevallen in plaats van een uitzondering te genereren. Een veelvoorkomende foutcase kan worden beschouwd als een normale controlestroom. Door in deze gevallen (of standaard) terug te keren null
, minimaliseert u de gevolgen voor de prestaties van een app.
Voor waardetypen kunt u overwegen of u deze wilt gebruiken Nullable<T>
of default
als foutindicator voor uw app. Met behulp van Nullable<Guid>
, default
wordt null
in plaats van Guid.Empty
. Soms kan het toevoegen Nullable<T>
het duidelijker maken wanneer een waarde aanwezig of afwezig is. In andere gevallen kan het toevoegen Nullable<T>
extra cases maken om te controleren of dat niet nodig is en alleen kan dienen om potentiële foutenbronnen te maken.
Herstelstatus wanneer methoden niet worden voltooid vanwege uitzonderingen
Bellers moeten kunnen aannemen dat er geen bijwerkingen zijn wanneer een uitzondering wordt gegenereerd vanuit een methode. Als u bijvoorbeeld code hebt die geld overdraagt door geld in te trekken van de ene rekening en het storten in een andere rekening, en er een uitzondering wordt gegenereerd tijdens het uitvoeren van de storting, wilt u niet dat de intrekking van kracht blijft.
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
Met de voorgaande methode worden geen uitzonderingen rechtstreeks gegooid. U moet echter de methode schrijven zodat de intrekking wordt omgekeerd als de stortingsbewerking mislukt.
Een manier om deze situatie te verwerken, is door eventuele uitzonderingen te ondervangen die door de stortingstransactie worden gegenereerd en de intrekking ongedaan te maken.
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
In dit voorbeeld ziet u het gebruik van het opnieuw plaatsen van throw
de oorspronkelijke uitzondering, waardoor bellers gemakkelijker de echte oorzaak van het probleem kunnen zien zonder de InnerException eigenschap te hoeven onderzoeken. Een alternatief is om een nieuwe uitzondering te genereren en de oorspronkelijke uitzondering op te nemen als de interne uitzondering.
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
Uitzonderingen goed vastleggen en opnieuw werpen
Zodra er een uitzondering wordt gegenereerd, is een deel van de informatie die wordt meegevoerd de stacktracering. De stacktracering is een lijst met de methode-aanroephiërarchie die begint met de methode die de uitzondering genereert en eindigt met de methode die de uitzondering onderschept. Als u een uitzondering opnieuw uitvoert door bijvoorbeeld de uitzondering in de throw
instructie throw e
op te geven, wordt de stacktracering opnieuw gestart bij de huidige methode en wordt de lijst met methodeaanroepen tussen de oorspronkelijke methode die de uitzondering heeft veroorzaakt en de huidige methode verloren gegaan. Als u de oorspronkelijke stack-traceringsgegevens met de uitzondering wilt behouden, zijn er twee opties die afhankelijk zijn van waar u de uitzondering wilt beperken:
- Als u de uitzondering vanuit de handler (
catch
blok) die de uitzonderingsexemplaren heeft gedetecteerd, opnieuw uitvoert, gebruikt u dethrow
instructie zonder de uitzondering op te geven. Met ca2200 voor codeanalyse kunt u plaatsen in uw code vinden waar u per ongeluk stack-traceringsgegevens kwijtraakt. - Als u de uitzondering vanaf een andere locatie dan de handler (
catch
blok) wilt beperken, gebruikt ExceptionDispatchInfo.Capture(Exception) u deze om de uitzondering vast te leggen in de handler en ExceptionDispatchInfo.Throw() wanneer u deze wilt verwijderen. U kunt de ExceptionDispatchInfo.SourceException eigenschap gebruiken om de vastgelegde uitzondering te inspecteren.
In het volgende voorbeeld ziet u hoe de ExceptionDispatchInfo klasse kan worden gebruikt en hoe de uitvoer eruit kan zien.
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();
Als het bestand in de voorbeeldcode niet bestaat, wordt de volgende uitvoer geproduceerd:
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
Uitzonderingen genereren
De volgende aanbevolen procedures hebben betrekking op de wijze waarop u uitzonderingen genereert:
- Vooraf gedefinieerde uitzonderingstypen gebruiken
- Methoden voor het maken van uitzonderingen gebruiken
- Een gelokaliseerd tekenreeksbericht opnemen
- De juiste grammatica gebruiken
- Gooiinstructies goed plaatsen
- Maak geen uitzonderingen in ten slotte clausules
- Geen uitzonderingen van onverwachte plaatsen genereren
- Validatie-uitzonderingen voor argumenten synchroon genereren
Vooraf gedefinieerde uitzonderingstypen gebruiken
Introduceer alleen een nieuwe uitzonderingsklasse wanneer een vooraf gedefinieerde klasse niet van toepassing is. Voorbeeld:
- Als een eigenschapsset of methodeaanroep niet geschikt is op basis van de huidige status van het object, genereert u een InvalidOperationException uitzondering.
- Als er ongeldige parameters worden doorgegeven, genereert u een ArgumentException uitzondering of een van de vooraf gedefinieerde klassen die zijn afgeleid van ArgumentException.
Notitie
Hoewel het het beste is om vooraf gedefinieerde uitzonderingstypen te gebruiken, moet u indien mogelijk geen gereserveerde uitzonderingstypen genereren, zoals AccessViolationException, NullReferenceExceptionIndexOutOfRangeExceptionen StackOverflowException. Zie CA2201 voor meer informatie : Geen gereserveerde uitzonderingstypen genereren.
Methoden voor het maken van uitzonderingen gebruiken
Het is gebruikelijk dat een klasse dezelfde uitzondering genereert op verschillende plaatsen in de implementatie. Als u overmatige code wilt voorkomen, maakt u een helpermethode waarmee de uitzondering wordt gemaakt en deze wordt geretourneerd. Voorbeeld:
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
Sommige belangrijke .NET-uitzonderingstypen hebben dergelijke statische throw
helpermethoden die de uitzondering toewijzen en genereren. U moet deze methoden aanroepen in plaats van het bijbehorende uitzonderingstype te maken en op te geven:
- 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
Tip
Met de volgende regels voor codeanalyse kunt u plaatsen in uw code vinden waar u kunt profiteren van deze statische throw
helpers: CA1510, CA1511, CA1512 en CA1513.
Als u een asynchrone methode implementeert, roept CancellationToken.ThrowIfCancellationRequested() u aan in plaats van te controleren of annulering is aangevraagd en vervolgens te bouwen en te OperationCanceledExceptiongooien. Zie CA2250 voor meer informatie.
Een gelokaliseerd tekenreeksbericht opnemen
Het foutbericht dat de gebruiker ziet, is afgeleid van de Exception.Message eigenschap van de uitzondering die is gegenereerd en niet van de naam van de uitzonderingsklasse. Normaal gesproken wijst u een waarde toe aan de Exception.Message eigenschap door de berichttekenreeks door te geven aan het message
argument van een uitzonderingsconstructor.
Voor gelokaliseerde toepassingen moet u een gelokaliseerde berichttekenreeks opgeven voor elke uitzondering die uw toepassing kan genereren. U gebruikt resourcebestanden om gelokaliseerde foutberichten op te geven. Zie de volgende artikelen voor informatie over het lokaliseren van toepassingen en het ophalen van gelokaliseerde tekenreeksen:
- Procedure: Door de gebruiker gedefinieerde uitzonderingen maken met gelokaliseerde uitzonderingsberichten
- Resources in .NET-apps
- System.Resources.ResourceManager
De juiste grammatica gebruiken
Duidelijke zinnen schrijven en leestekens beëindigen. Elke zin in de tekenreeks die aan de Exception.Message eigenschap is toegewezen, moet in een punt eindigen. Bijvoorbeeld: 'De logboektabel is overgelopen'. Gebruikt de juiste grammatica en leestekens.
Gooiinstructies goed plaatsen
Plaats throw-instructies waar de stack-trace nuttig is. De stack-trace begint bij de instructie waarin de uitzondering wordt gegenereerd en eindigt bij de catch
instructie die de uitzondering onderschept.
Maak geen uitzonderingen in ten slotte clausules
Verhef geen uitzonderingen in finally
componenten. Zie codeanalyseregel CA2219 voor meer informatie.
Geen uitzonderingen van onverwachte plaatsen genereren
Sommige methoden, zoals Equals
, GetHashCode
en ToString
methoden, statische constructors en gelijkheidsoperators, mogen geen uitzonderingen genereren. Zie codeanalyseregel CA1065 voor meer informatie.
Validatie-uitzonderingen voor argumenten synchroon genereren
In methoden voor het retourneren van taken moet u argumenten valideren en eventuele bijbehorende uitzonderingen genereren, zoals ArgumentException en ArgumentNullException, voordat u het asynchrone deel van de methode invoert. Uitzonderingen die worden gegenereerd in het asynchrone deel van de methode, worden opgeslagen in de geretourneerde taak en komen pas voor wanneer de taak bijvoorbeeld wordt gewacht. Zie Uitzonderingen in methoden voor het retourneren van taken voor meer informatie.
Aangepaste uitzonderingstypen
De volgende aanbevolen procedures hebben betrekking op aangepaste uitzonderingstypen:
- Namen van uitzonderingsklassen beëindigen met
Exception
- Drie constructors opnemen
- Geef indien nodig aanvullende eigenschappen op
Namen van uitzonderingsklassen beëindigen met Exception
Wanneer een aangepaste uitzondering nodig is, moet u deze op de juiste wijze een naam opgeven en afleiden uit de Exception klasse. Voorbeeld:
public class MyFileNotFoundException : Exception
{
}
Public Class MyFileNotFoundException
Inherits Exception
End Class
Drie constructors opnemen
Gebruik ten minste de drie algemene constructors bij het maken van uw eigen uitzonderingsklassen: de parameterloze constructor, een constructor die een tekenreeksbericht gebruikt en een constructor die een tekenreeksbericht en een interne uitzondering accepteert.
- Exception(), waarbij standaardwaarden worden gebruikt.
- Exception(String), waarmee een tekenreeksbericht wordt geaccepteerd.
- Exception(String, Exception), waarmee een tekenreeksbericht en een interne uitzondering worden geaccepteerd.
Zie Voor een voorbeeld : Door de gebruiker gedefinieerde uitzonderingen maken.
Geef indien nodig aanvullende eigenschappen op
Geef aanvullende eigenschappen op voor een uitzondering (naast de aangepaste berichttekenreeks) alleen als er een programmatisch scenario is waarin de aanvullende informatie nuttig is. De eigenschap wordt bijvoorbeeld FileNotFoundException opgegeven FileName .