Dela via


Metodtips för undantag

Korrekt undantagshantering är viktigt för programmets tillförlitlighet. Du kan avsiktligt hantera förväntade undantag för att förhindra att appen kraschar. En kraschad app är dock mer tillförlitlig och kan diagnostiseras än en app med odefinierat beteende.

I den här artikeln beskrivs metodtips för att hantera och skapa undantag.

Hantering av undantag

Följande metodtips gäller hur du hanterar undantag:

Använd try/catch/finally-block för att återställa från fel eller frigöra resurser

För kod som potentiellt kan generera ett undantag och när din app kan återställas från det undantaget använder du try/catch block runt koden. I catch block beställer du alltid undantag från de mest härledda till de minst härledda. (Alla undantag härleds från Exception klassen. Fler härledda undantag hanteras inte av en catch sats som föregås av en catch sats för en bas undantagsklass.) När koden inte kan återställas från ett undantag ska du inte fånga det undantaget. Aktivera metoder längre upp i anropsstacken för att återställa om möjligt.

Rensa resurser som allokeras med antingen using instruktioner eller finally block. Föredrar using instruktioner för att automatiskt rensa resurser när undantag utlöses. Använd finally block för att rensa resurser som inte implementerar IDisposable. Kod i en finally sats körs nästan alltid även när undantag utlöses.

Hantera vanliga villkor för att undvika undantag

För villkor som sannolikt inträffar men som kan utlösa ett undantag kan du överväga att hantera dem på ett sätt som undviker undantaget. Om du till exempel försöker stänga en anslutning som redan är stängd får du en InvalidOperationException. Du kan undvika det genom att använda en if instruktion för att kontrollera anslutningstillståndet innan du försöker stänga det.

if (conn.State != ConnectionState.Closed)
{
    conn.Close();
}
If conn.State <> ConnectionState.Closed Then
    conn.Close()
End IF

Om du inte kontrollerar anslutningstillståndet innan du stänger kan du fånga undantaget 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

Vilken metod du ska välja beror på hur ofta du förväntar dig att händelsen ska inträffa.

  • Använd undantagshantering om händelsen inte inträffar ofta, dvs. om händelsen verkligen är exceptionell och indikerar ett fel, till exempel ett oväntat filslut. När du använder undantagshantering körs mindre kod under normala förhållanden.

  • Kontrollera om det finns feltillstånd i koden om händelsen inträffar rutinmässigt och kan betraktas som en del av normal körning. När du söker efter vanliga feltillstånd körs mindre kod eftersom du undviker undantag.

    Kommentar

    Kontroller i förväg eliminerar undantag för det mesta. Det kan dock finnas konkurrensförhållanden där det skyddade villkoret ändras mellan kontrollen och åtgärden, och i så fall kan du fortfarande ådra dig ett undantag.

Anropa Try* metoder för att undvika undantag

Om prestandakostnaden för undantag är oöverkomlig tillhandahåller vissa .NET-biblioteksmetoder alternativa former av felhantering. Till exempel Int32.Parse genererar ett OverflowException om värdet som ska parsas är för stort för att representeras av Int32. Det här undantaget utlöser dock Int32.TryParse inte det här undantaget. I stället returnerar den ett booleskt värde och har en out parameter som innehåller det parsade giltiga heltalet när det lyckas. Dictionary<TKey,TValue>.TryGetValue har liknande beteende för att försöka hämta ett värde från en ordlista.

Fånga annullering och asynkrona undantag

Det är bättre att fånga OperationCanceledException i stället för TaskCanceledException, som härleds från OperationCanceledException, när du anropar en asynkron metod. Många asynkrona metoder utlöser ett OperationCanceledException undantag om annullering begärs. Dessa undantag gör att körningen effektivt kan stoppas och anropstacken återställs när en annulleringsbegäran har observerats.

Asynkrona metoder lagrar undantag som utlöses under körningen i den aktivitet som de returnerar. Om ett undantag lagras i den returnerade aktiviteten utlöses undantaget när aktiviteten väntar. Användningsfel, till exempel ArgumentException, genereras fortfarande synkront. Mer information finns i Asynkrona undantag.

Utforma klasser så att undantag kan undvikas

En klass kan tillhandahålla metoder eller egenskaper som gör att du kan undvika att göra ett anrop som utlöser ett undantag. Klassen innehåller till exempel FileStream metoder som hjälper dig att avgöra om slutet av filen har nåtts. Du kan anropa dessa metoder för att undvika undantaget som utlöses om du läser förbi slutet av filen. I följande exempel visas hur du läser till slutet av en fil utan att utlösa ett undantag:

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

Ett annat sätt att undvika undantag är att returnera null (eller standard) för de vanligaste felfallen i stället för att utlösa ett undantag. Ett vanligt felfall kan betraktas som ett normalt kontrollflöde. Genom att null returnera (eller standard) i dessa fall minimerar du prestandapåverkan för en app.

För värdetyper bör du överväga om du vill använda Nullable<T> eller default som felindikator för din app. Genom att använda Nullable<Guid>blir default i null stället för Guid.Empty. Ibland kan tillägg Nullable<T> göra det tydligare när ett värde finns eller saknas. Andra gånger kan tillägg Nullable<T> skapa extra ärenden för att kontrollera att de inte är nödvändiga och endast användas för att skapa potentiella felkällor.

Återställningstillstånd när metoderna inte slutförs på grund av undantag

Anropare bör kunna anta att det inte finns några biverkningar när ett undantag genereras från en metod. Om du till exempel har kod som överför pengar genom att ta ut från ett konto och sätta in på ett annat konto, och ett undantag utlöses när insättningen utförs, vill du inte att uttaget ska gälla.

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

Föregående metod utlöser inga undantag direkt. Du måste dock skriva metoden så att uttagen återförs om insättningsåtgärden misslyckas.

Ett sätt att hantera den här situationen är att fånga upp eventuella undantag som utlöses av insättningstransaktionen och återställa uttagen.

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

Det här exemplet illustrerar användningen av throw för att återväxa det ursprungliga undantaget, vilket gör det lättare för anropare att se den verkliga orsaken till problemet utan att behöva undersöka InnerException egenskapen. Ett alternativ är att utlösa ett nytt undantag och inkludera det ursprungliga undantaget som det inre undantaget.

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

Fånga in och återväsna undantag korrekt

När ett undantag har genererats är en del av den information som det bär stackspårningen. Stackspårningen är en lista över metodanropshierarkin som börjar med metoden som genererar undantaget och slutar med metoden som fångar undantaget. Om du återaktiverar ett undantag genom att ange undantaget i -instruktionen throw , throw etill exempel , startas stackspårningen om med den aktuella metoden och listan över metodanrop mellan den ursprungliga metoden som utlöste undantaget och den aktuella metoden går förlorad. För att behålla den ursprungliga stackspårningsinformationen med undantaget finns det två alternativ som är beroende av var du återväxtar undantaget från:

  • Om du återväxar undantaget från hanteraren (catch blocket) som fångas undantagsinstansen använder du -instruktionen throw utan att ange undantaget. Kodanalysregeln CA2200 hjälper dig att hitta platser i koden där du oavsiktligt kan förlora stackspårningsinformation.
  • Om du återväxar undantaget från någon annan plats än hanteraren (catch blocket) använder ExceptionDispatchInfo.Capture(Exception) du för att avbilda undantaget i hanteraren och ExceptionDispatchInfo.Throw() när du vill återväxa det. Du kan använda egenskapen ExceptionDispatchInfo.SourceException för att inspektera det insamlade undantaget.

I följande exempel visas hur ExceptionDispatchInfo klassen kan användas och hur utdata kan se ut.

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();

Om filen i exempelkoden inte finns skapas följande utdata:

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

Utlösa undantag

Följande metodtips gäller hur du utlöser undantag:

Använda fördefinierade undantagstyper

Introducera endast en ny undantagsklass när en fördefinierad klass inte gäller. Till exempel:

  • Om en egenskapsuppsättning eller ett metodanrop inte är lämpligt med tanke på objektets aktuella tillstånd utlöser du ett InvalidOperationException undantag.
  • Om ogiltiga parametrar skickas utlöser du ett ArgumentException undantag eller någon av de fördefinierade klasserna som härleds från ArgumentException.

Kommentar

Även om det är bäst att använda fördefinierade undantagstyper när det är möjligt bör du inte skapa några reserverade undantagstyper, till exempel AccessViolationException, NullReferenceExceptionIndexOutOfRangeExceptionoch StackOverflowException. Mer information finns i CA2201: Skapa inte reserverade undantagstyper.

Använda undantagsverktygets metoder

Det är vanligt att en klass genererar samma undantag från olika platser i implementeringen. Undvik överdriven kod genom att skapa en hjälpmetod som skapar undantaget och returnerar det. Till exempel:

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

Vissa viktiga .NET-undantagstyper har sådana statiska throw hjälpmetoder som allokerar och genererar undantaget. Du bör anropa dessa metoder i stället för att skapa och utlösa motsvarande undantagstyp:

Dricks

Följande kodanalysregler kan hjälpa dig att hitta platser i koden där du kan dra nytta av dessa statiska throw hjälpverktyg: CA1510, CA1511, CA1512 och CA1513.

Om du implementerar en asynkron metod anropar CancellationToken.ThrowIfCancellationRequested() du i stället för att kontrollera om annullering begärdes och sedan konstruerar och genererar OperationCanceledException. Mer information finns i CA2250.

Inkludera ett lokaliserat strängmeddelande

Felmeddelandet som användaren ser härleds från Exception.Message egenskapen för undantaget som utlöstes och inte från namnet på undantagsklassen. Vanligtvis tilldelar du ett värde till Exception.Message egenskapen genom att skicka meddelandesträngen message till argumentet för en undantagskonstruktor.

För lokaliserade program bör du ange en lokaliserad meddelandesträng för varje undantag som programmet kan utlösa. Du använder resursfiler för att ange lokaliserade felmeddelanden. Information om hur du lokaliserar program och hämtar lokaliserade strängar finns i följande artiklar:

Använd korrekt grammatik

Skriv tydliga meningar och inkludera avslutande skiljetecken. Varje mening i strängen som tilldelats egenskapen Exception.Message ska sluta i en period. Till exempel använder "Loggtabellen har spillts över" rätt grammatik och skiljetecken.

Placera utkastsuttryck väl

Placera throw-instruktioner där stackspårningen är till hjälp. Stackspårningen börjar vid -instruktionen där undantaget genereras och slutar vid -instruktionen catch som fångar undantaget.

Skapa inte undantag i slutsatser

Skapa inte undantag i finally satser. Mer information finns i kodanalysregeln CA2219.

Skapa inte undantag från oväntade platser

Vissa metoder, till exempel Equals, GetHashCodeoch ToString metoder, statiska konstruktorer och likhetsoperatorer, bör inte utlösa undantag. Mer information finns i kodanalysregeln CA1065.

Generera argumentverifieringsfel synkront

I metoder för uppgiftsretur bör du verifiera argument och utlösa eventuella motsvarande undantag, till exempel ArgumentException och ArgumentNullException, innan du anger den asynkrona delen av metoden. Undantag som genereras i den asynkrona delen av metoden lagras i den returnerade aktiviteten och visas inte förrän uppgiften till exempel väntar. Mer information finns i Undantag i metoder för uppgiftsretur.

Anpassade undantagstyper

Följande metodtips gäller anpassade undantagstyper:

Avsluta undantagsklassnamn med Exception

När ett anpassat undantag krävs namnger du det på rätt sätt och härleder det från Exception klassen. Till exempel:

public class MyFileNotFoundException : Exception
{
}
Public Class MyFileNotFoundException
    Inherits Exception
End Class

Inkludera tre konstruktorer

Använd minst tre vanliga konstruktorer när du skapar dina egna undantagsklasser: den parameterlösa konstruktorn, en konstruktor som tar ett strängmeddelande och en konstruktor som tar ett strängmeddelande och ett inre undantag.

Ett exempel finns i How to: Create user-defined exceptions (Så här skapar du användardefinierade undantag).

Ange ytterligare egenskaper efter behov

Ange ytterligare egenskaper för ett undantag (utöver den anpassade meddelandesträngen) endast när det finns ett programmatiskt scenario där ytterligare information är användbar. Till FileNotFoundException exempel tillhandahåller FileName egenskapen .

Se även