Osvědčené postupy pro výjimky

Správné zpracování výjimek je nezbytné pro spolehlivost aplikací. Můžete záměrně zpracovat očekávané výjimky, abyste zabránili chybovému ukončení aplikace. Aplikace s chybovým ukončením je ale spolehlivější a diagnostikovatelná než aplikace s nedefinovaným chováním.

Tento článek popisuje osvědčené postupy pro zpracování a vytváření výjimek.

Zpracování výjimek

Následující osvědčené postupy se týkají způsobu zpracování výjimek:

Použití bloků try/catch/finally k zotavení z chyb nebo prostředků vydaných verzí

Pro kód, který může potenciálně vygenerovat výjimku, a když se vaše aplikace může z této výjimky zotavit, použijte try/catch kolem kódu bloky. V catch blocích vždy objednávejte výjimky z nejvíce odvozených na nejméně odvozené. (Všechny výjimky jsou odvozeny od Exception třídy. Další odvozené výjimky nejsou zpracovávány catch klauzulí, která předchází catch klauzuli základní třídy výjimky.) Pokud se váš kód nemůže obnovit z výjimky, nezachyťte tuto výjimku. Povolte metody dále v zásobníku volání, aby se v případě potřeby obnovily.

Vyčistěte prostředky, které jsou přiděleny příkazy using nebo finally bloky. Upřednostňujte using příkazy k automatickému vyčištění prostředků, když dojde k vyvolání výjimek. Pomocí finally bloků vyčistěte prostředky, které neimplementují IDisposable. Kód v finally klauzuli se téměř vždy spustí i v případě, že dojde k vyvolání výjimek.

Zpracování běžných podmínek, aby nedocházelo k výjimkám

U podmínek, u kterých je pravděpodobné, že dojde k výjimce, ale mohou vyvolat výjimku, zvažte jejich zpracování způsobem, který se této výjimce zabrání. Pokud se například pokusíte zavřít připojení, které je již uzavřeno, získáte .InvalidOperationException Tomu se můžete vyhnout použitím if příkazu ke kontrole stavu připojení před pokusem o jeho zavření.

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

Pokud před zavřením nekontrolujete stav připojení, můžete výjimku zachytit 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

Způsob výběru závisí na tom, jak často očekáváte výskyt události.

  • Zpracování výjimek použijte, pokud k události často nedojde, to znamená, že pokud je událost skutečně výjimečná a značí chybu, například neočekávaný konec souboru. Při používání zpracování výjimek je za běžných podmínek provedena menší část kódu.

  • Zkontrolujte chybové stavy v kódu, pokud se událost děje rutinně, a je možné ji považovat za součást normálního spuštění. Při kontrole běžných chybových podmínek se spustí méně kódu, protože se vyhnete výjimkám.

    Poznámka:

    Počáteční kontroly většinu času eliminují výjimky. Můžou však existovat podmínky časování, kdy se strážený stav změní mezi kontrolou a operací, a v takovém případě může dojít k výjimce.

Volání Try* metod pro zabránění výjimkám

Pokud jsou náklady na výkon výjimek zakázané, některé metody knihovny .NET poskytují alternativní formy zpracování chyb. Například vyvolá hodnotu, Int32.Parse která má být analyzována, je příliš velká, aby byla reprezentována Int32.OverflowException Int32.TryParse Tato výjimka však nevyvolá. Místo toho vrátí logickou hodnotu a má out parametr, který obsahuje analyzované platné celé číslo při úspěchu. Dictionary<TKey,TValue>.TryGetValue má podobné chování při pokusu o získání hodnoty ze slovníku.

Zachycení zrušení a asynchronních výjimek

Při volání asynchronní metody je lepší zachytit OperationCanceledException místo TaskCanceledException, která je odvozena z OperationCanceledException. Mnoho asynchronních metod vyvolá OperationCanceledException výjimku, pokud je požadováno zrušení. Tyto výjimky umožňují efektivní zastavení spuštění a zrušení volání vysunout, jakmile se zobrazí požadavek na zrušení.

Asynchronní metody ukládají výjimky, které jsou vyvolány během provádění v úkolu, který vrací. Pokud je výjimka uložena do vrácené úlohy, tato výjimka bude vyvolána, když je úkol očekáván. Výjimky použití, například ArgumentException, jsou stále vyvolány synchronně. Další informace naleznete v tématu Asynchronní výjimky.

Třídy návrhu tak, aby se výjimky mohly vyhnout

Třída může poskytovat metody nebo vlastnosti, které umožňují vyhnout se volání, které by aktivovalo výjimku. Třída například poskytuje metody, které pomáhají určit, FileStream zda byl dosažen konec souboru. Tyto metody můžete volat, abyste se vyhnuli výjimce, která se vyvolá, pokud jste si přečetli konec souboru. Následující příklad ukazuje, jak číst na konec souboru bez aktivace výjimky:

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

Dalším způsobem, jak se vyhnout výjimkám, je vrátit null (nebo výchozí) pro nejběžnější případy chyb místo vyvolání výjimky. Běžný případ chyby lze považovat za normální tok řízení. Vrácením (nebo výchozího null ) v těchto případech minimalizujete dopad na výkon aplikace.

U typů hodnot zvažte, jestli se má aplikace použít Nullable<T> nebo default jako indikátor chyby. Použitím Nullable<Guid>se default místo nullGuid.Empty. Někdy může přidání Nullable<T> usnadnit, když je hodnota přítomná nebo chybí. Jindy může přidání Nullable<T> vytvořit další případy, které kontrolují, že nejsou nezbytné, a slouží pouze k vytváření potenciálních zdrojů chyb.

Obnovení stavu v případech, kdy se metody nedokončí kvůli výjimkám

Volající by měl předpokládat, že při vyvolání výjimky z metody nedojde k žádným vedlejším účinkům. Pokud máte například kód, který převádí peníze stažením z jednoho účtu a vkladem na jiný účet a při provádění vkladu dojde k výjimce, nechcete, aby výběr zůstal v platnosti.

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

Předchozí metoda přímo nevyvolá žádné výjimky. Je však nutné napsat metodu tak, aby se výběr v případě selhání operace vkladu vrátit zpět.

Jedním ze způsobů, jak tuto situaci vyřešit, je zachytit všechny výjimky vyvolané vkladem transakce a vrátit zpět výběr.

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

Tento příklad ukazuje použití throw k opětovnému rozvětvování původní výjimky, což usnadňuje volajícím vidět skutečnou příčinu problému bez nutnosti zkoumat InnerException vlastnost. Alternativou je vyvolání nové výjimky a zahrnutí původní výjimky jako vnitřní výjimky.

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

Správně zachytávat a znovu načítat výjimky

Po vyvolání výjimky je součástí informací, které přenáší, trasování zásobníku. Trasování zásobníku je seznam hierarchie volání metody, která začíná metodou, která vyvolá výjimku a končí metodou, která zachytí výjimku. Pokud znovu načítáte výjimku zadáním výjimky v throw příkazu, throw enapříklad , trasování zásobníku se restartuje v aktuální metodě a seznam volání metody mezi původní metodou, která vyvolala výjimku a aktuální metoda je ztracena. Pokud chcete zachovat původní informace o trasování zásobníku s výjimkou, existují dvě možnosti, které závisí na tom, odkud výjimku vytváříte:

  • Pokud znovu zvětšujete výjimku z obslužné rutiny (catch bloku), která zachytila instanci výjimky, použijte throw příkaz bez určení výjimky. Pravidlo analýzy kódu CA2200 vám pomůže najít místa v kódu, kde můžete neúmyslně ztratit informace o trasování zásobníku.
  • Pokud výjimku znovu načítáte z jiného než obslužné rutiny (catch bloku), použijte ExceptionDispatchInfo.Capture(Exception) k zachycení výjimky v obslužné rutině a ExceptionDispatchInfo.Throw() jejím opětovném rozšíření. Tuto vlastnost můžete použít ExceptionDispatchInfo.SourceException ke kontrole zachycené výjimky.

Následující příklad ukazuje, jak ExceptionDispatchInfo lze třídu použít a jak může vypadat výstup.

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

Pokud soubor v ukázkovém kódu neexistuje, vytvoří se následující výstup:

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

Vyvolání výjimek

Následující osvědčené postupy se týkají způsobu vyvolání výjimek:

Použití předdefinovaných typů výjimek

Zavést novou třídu výjimek pouze v případech, kdy se nepoužívá předdefinovaná třída. Příklad:

Poznámka:

I když je nejlepší použít předdefinované typy výjimek, pokud je to možné, neměli byste vyvolat některé rezervované typy výjimek, například AccessViolationException, IndexOutOfRangeExceptionNullReferenceException a StackOverflowException. Další informace naleznete v tématu CA2201: Nevyvolávejte rezervované typy výjimek.

Použití metod tvůrce výjimek

Je běžné, že třída vyvolá stejnou výjimku z různých míst ve své implementaci. Pokud se chcete vyhnout nadměrnému kódu, vytvořte pomocnou metodu, která vytvoří výjimku a vrátí ji. Příklad:

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

Některé klíčové typy výjimek .NET mají takové statické throw pomocné metody, které přidělují a vyvolají výjimku. Místo vytváření a vyvolání odpovídajícího typu výjimky byste měli volat tyto metody:

Tip

Následující pravidla analýzy kódu vám pomůžou najít místa v kódu, kde můžete využít tyto statické throw pomocné rutiny: CA1510, CA1511, CA1512 a CA1513.

Pokud implementujete asynchronní metodu, místo CancellationToken.ThrowIfCancellationRequested() kontroly, zda bylo požadováno zrušení, a pak vytváření a vyvolání OperationCanceledException. Další informace najdete v tématu CA2250.

Zahrnutí lokalizované řetězcové zprávy

Chybová zpráva, která se uživateli zobrazí, je odvozena z Exception.Message vlastnosti vyvolané výjimky, a ne z názvu třídy výjimky. Obvykle přiřadíte hodnotu vlastnosti Exception.Message předáním řetězce zprávy argumentu message exception konstruktoru.

U lokalizovaných aplikací byste měli zadat lokalizovaný řetězec zprávy pro každou výjimku, kterou může aplikace vyvolat. Soubory prostředků slouží k poskytování lokalizovaných chybových zpráv. Informace o lokalizaci aplikací a načítání lokalizovaných řetězců najdete v následujících článcích:

Použití správné gramatiky

Napište jasné věty a zahrňte koncovou interpunkci. Každá věta v řetězci přiřazené vlastnosti Exception.Message by měla končit tečkou. Například tabulka protokolu přetečení používá správnou gramatiku a interpunkci.

Umístit příkazy throw dobře

Umístěte příkazy throw, kde bude užitečné trasování zásobníku. Trasování zásobníku začíná příkazem, kde je vyvolán výjimka a končí příkazem catch , který zachytí výjimku.

Nevyvolávejte výjimky v klauzulích finally

Nevyvolávejte výjimky v finally klauzulích. Další informace najdete v tématu Pravidlo analýzy kódu CA2219.

Nevyvolávejte výjimky z neočekávaných míst

Některé metody, jako Equalsjsou , GetHashCodea ToString metody, statické konstruktory a operátory rovnosti, by neměly vyvolat výjimky. Další informace najdete v článku Pravidla analýzy kódu CA1065.

Synchronní vyvolání výjimek ověření argumentu

V metodách vracejících úkoly byste měli před zadáním asynchronní části metody ověřit argumenty a vyvolat všechny odpovídající výjimky, například ArgumentException a ArgumentNullException. Výjimky, které jsou vyvolány v asynchronní části metody, jsou uloženy ve vrácené úloze a neobjevují se, dokud například úkol nebude očekáván. Další informace naleznete v tématu Výjimky v metodách vracejících úlohu.

Vlastní typy výjimek

Následující osvědčené postupy se týkají vlastních typů výjimek:

Ukončit názvy tříd výjimek pomocí Exception

Pokud je potřeba vlastní výjimka, pojmenujte ji odpovídajícím způsobem a odvozujte ji z Exception třídy. Příklad:

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

Zahrnout tři konstruktory

Při vytváření vlastních tříd výjimek použijte alespoň tři běžné konstruktory: konstruktor bez parametrů, konstruktor, který přebírá řetězcovou zprávu, a konstruktor, který přebírá řetězcovou zprávu a vnitřní výjimku.

Příklad najdete v tématu Postupy: Vytvoření uživatelem definovaných výjimek.

Podle potřeby zadejte další vlastnosti.

Uveďte další vlastnosti výjimky (kromě vlastního řetězce zprávy) jenom v případě, že existuje programový scénář, ve kterém jsou další informace užitečné. Například FileNotFoundException poskytuje FileName vlastnost.

Viz také