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

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 OperationCanceledExceptionTaskCanceledException, 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 eop 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 de throw 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

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:

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:

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, GetHashCodeen 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

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.

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 .

Zie ook