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í
- Zpracování běžných podmínek, aby nedocházelo k výjimkám
- Zachycení zrušení a asynchronních výjimek
- Třídy návrhu tak, aby se výjimky mohly vyhnout
- Obnovení stavu v případech, kdy se metody nedokončí kvůli výjimkám
- Správně zachytávat a znovu načítat výjimky
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 null
Guid.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 e
napří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žijtethrow
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
- Použití metod tvůrce výjimek
- Zahrnutí lokalizované řetězcové zprávy
- Použití správné gramatiky
- Umístit příkazy throw dobře
- Nevyvolávejte výjimky v klauzulích finally
- Nevyvolávejte výjimky z neočekávaných míst
- Synchronní vyvolání výjimek ověření argumentu
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:
- Pokud není volání vlastnosti nebo metody vhodné vzhledem k aktuálnímu stavu objektu, vyvolá výjimku InvalidOperationException .
- Pokud jsou předány neplatné parametry, vyvolá ArgumentException výjimku nebo jednu z předdefinovaných tříd odvozených z ArgumentException.
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:
- 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
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:
- Postupy: Vytváření uživatelem definovaných výjimek s lokalizovanými zprávami výjimek
- Prostředky v aplikacích .NET
- System.Resources.ResourceManager
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 Equals
jsou , GetHashCode
a 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
- Zahrnout tři konstruktory
- Podle potřeby zadejte další vlastnosti.
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.
- Exception(), která používá výchozí hodnoty.
- Exception(String), který přijímá řetězcovou zprávu.
- Exception(String, Exception), který přijímá ř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.