Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Správné zpracování výjimek je nezbytné pro spolehlivost aplikací. Očekávané výjimky můžete záměrně zpracovat, 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 uvolnění prostředků
- Zpracování běžných podmínek, aby nedocházelo k výjimkám
- Zachytávání zrušení a asynchronních výjimek
- třídy návrhu tak, aby výjimky mohly být
- Obnovit stav, když se metody nedokončí kvůli výjimkám
- Správně zachytávat a znovu vyvolávat výjimky
Použijte bloky try/catch/finally k zotavení se z chyb nebo uvolnění prostředků.
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
bloků kolem kódu. V catch
blocích vždy uspořádejte výjimky od nejvíce odvozených k nejméně odvozeným. (Všechny výjimky jsou odvozeny z Exception třídy. Další odvozené výjimky nejsou zpracovávány klauzulí catch
, která předchází klauzuli catch
pro základní třídu výjimky.) Pokud se váš kód nemůže obnovit z výjimky, nezachyťte tuto výjimku. Povolte metody vyšší úrovně ve volacím zásobníku, aby se mohly v případě potřeby obnovit.
Vyčistěte prostředky, které byly přiděleny buď pomocí příkazů using
, nebo bloků finally
. Upřednostněte příkazy using
k automatickému vyčištění prostředků při vyvolání výjimek. Pomocí bloků finally
vyčistěte prostředky, které neimplementují IDisposable. Kód v klauzuli finally
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é už je zavřené, zobrazí se InvalidOperationException
. Tomu se můžete vyhnout pomocí příkazu if
, abyste před pokusem o jeho zavření zkontrolovali stav připojení.
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 zachytit výjimku 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žití zpracování výjimek se v normálních podmínkách spustí méně 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 souběhu, kdy se chráněný stav změní mezi kontrolou a operací, a v takovém případě může dojít k výjimce.
Zavolejte metody Try*
, aby se zabránilo výjimkám.
Pokud jsou náklady na výkon výjimek neúnosně vysoké, některé metody knihovny .NET poskytují alternativní formy zpracování chyb. Například Int32.Parse vyvolá OverflowException, pokud je hodnota, kterou chcete analyzovat, příliš velká, aby byla reprezentována Int32.
Int32.TryParse však tuto výjimku nevyvolá. Místo toho vrátí logickou hodnotu a má parametr out
, který při úspěchu obsahuje analyzované platné celé číslo.
Dictionary<TKey,TValue>.TryGetValue má podobné chování při pokusu o získání hodnoty ze slovníku.
Zachytávání zrušení a asynchronních výjimek
Je lepší zachytit OperationCanceledException místo TaskCanceledException, která je odvozena z OperationCanceledException
, při volání asynchronní metody. Mnoho asynchronních metod vyvolá výjimku OperationCanceledException, pokud je požadováno zrušení. Tyto výjimky umožňují efektivní zastavení provádění a rozvinout zásobník volání, jakmile je zaznamenán požadavek na zrušení.
Asynchronní metody ukládají výjimky, které jsou vyvolány během provádění, do úkolu, který vrací. Pokud je výjimka uložena do vráceného úkolu, tato výjimka bude vyvolána při čekání na úkol. Výjimky použití, například ArgumentException, jsou stále vyvolány synchronně. Další informace naleznete v sekci Asynchronní výjimky.
Navrhujte třídy tak, aby se dalo vyhnout výjimkám
Třída může poskytovat metody nebo vlastnosti, které umožňují vyhnout se volání, které by aktivovalo výjimku. Například třída FileStream poskytuje metody, které pomáhají určit, zda byl dosažen konec souboru. Tyto metody můžete volat, abyste se vyhnuli výjimce, která je vyvolána, pokud čtete za koncem 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 null
(nebo výchozí) v těchto případech minimalizujete dopad na výkon aplikace.
U typů hodnot zvažte, jestli jako indikátor chyby aplikace použít Nullable<T>
nebo default
. Pomocí Nullable<Guid>
se default
místo null
stává Guid.Empty
. Někdy může přidání Nullable<T>
pomoci jasněji určit, zda je hodnota přítomná, nebo chybí. Jindy může přidání Nullable<T>
vytvořit další případy pro kontrolu, které nejsou nezbytné a slouží pouze k vytvoření potenciálních zdrojů chyb.
Obnovení stavu v případech, kdy se metody nedokončí kvůli výjimkám
Volající by měli být schopni předpokládat, že neexistují žádné vedlejší účinky, pokud je hozena výjimka z metody. 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 peněz v případě selhání operace vkladu vrátil zpět.
Jedním ze způsobů, jak tuto situaci vyřešit, je zachytit všechny výjimky vyvolané transakcí vkladu a vrátit výběr zpět.
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 vytvoření původní výjimky, což usnadňuje volajícím vidět skutečnou příčinu problému, aniž by museli zkoumat vlastnost InnerException. 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 vyvolat 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 vyvoláte výjimku zadáním výjimky v příkazu throw
, například throw e
, sledování zásobníku se restartuje v aktuální metodě a seznam volání metod mezi původní metodou, která vyvolala výjimku, a aktuální metodou se ztratí. 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 znovu vyvoláváte:
- Pokud znovu vyvoláte výjimku z bloku obslužné rutiny (
catch
), který zachytil instanci výjimky, použijte příkazthrow
bez zadání výjimky. Pravidlo analýzy kódu CA2200 pomáhá najít místa v kódu, kde byste neúmyslně ztratili informace o trasování zásobníku. - Pokud přehazujete výjimku z jiného místa než z obslužné rutiny (
catch
blok), použijte ExceptionDispatchInfo.Capture(Exception) k zachycení výjimky v obslužné rutině a ExceptionDispatchInfo.Throw(), když ji chcete znovu přehodit. K kontrole zachycené výjimky můžete použít vlastnost ExceptionDispatchInfo.SourceException.
Následující příklad ukazuje, jak lze použít třídu ExceptionDispatchInfo a jak může výstup vypadat.
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
- Zahrnout lokalizovanou řetězcovou zprávu
- Používejte správnou gramatiku
- Vhodně umísťujte throw statementy
- Nevyvolávejte výjimky v klauzulích finally
- Nevyvolávejte výjimky z neočekávaných míst
- Vyvolání výjimek ověřování argumentů synchronně
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. Například:
- Pokud není volání vlastnosti nebo metody vhodné vzhledem k aktuálnímu stavu objektu, vyvolá InvalidOperationException výjimku.
- 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 to možné, nejlepší je použít předdefinované typy výjimek, neměli byste vyvolat některé rezervované typy výjimek, jako jsou AccessViolationException, IndexOutOfRangeException, NullReferenceException 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 ve své implementaci vyvolává stejnou výjimku z různých míst. Pokud se chcete vyhnout nadměrnému kódu, vytvořte pomocnou metodu, která vytvoří výjimku a vrátí ji. Napří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 v .NET mají statické pomocné metody throw
, které přidělují a vyvolávají 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
Spropitné
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, CA1512a CA1513.
Pokud implementujete asynchronní metodu, volejte CancellationToken.ThrowIfCancellationRequested() místo kontroly, zda bylo požadováno zrušení a následného vytvoření a vyvolání OperationCanceledException. Pro více informací viz CA2250.
Zahrňte lokalizovanou textovou zprávu
Chybová zpráva, kterou uživatel uvidí, je odvozena z Exception.Message vlastnosti výjimky, která byla vyvolána, a ne z názvu třídy výjimky. Obvykle můžete přiřadit hodnotu vlastnosti Exception.Message předáním textu zprávy message
do argumentu konstruktoru Exception.
U lokalizovaných aplikací byste měli zadat lokalizovaný řetězec zprávy pro každou výjimku, kterou může aplikace vyvolat. Používáte soubory prostředků 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 o výjimkách
- 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ému k vlastnosti Exception.Message by měla končit tečkou. Například věta "Tabulka protokolu přetekla." používá správnou gramatiku a interpunkci.
Dobře umístit příkazy throw
Umístěte příkazy throw tam, kde trasování zásobníku pomůže. Trasování zásobníku začíná příkazem, kde je vyvolána výjimka, a končí u příkazu catch
, který zachytí výjimku.
Nevyvolávejte výjimky v klauzulích finally
Nevyvolávejte výjimky v klauzulích finally
. Další informace naleznete v tématu pravidla analýzy kódu CA2219.
Nevyvolávejte výjimky z neočekávaných míst
Některé metody, jako jsou Equals
, GetHashCode
a ToString
metody, statické konstruktory a operátory rovnosti, by neměly vyvolat výjimky. Další informace naleznete v tématu pravidla analýzy kódu CA1065.
Synchronní vyvolání výjimek ověření argumentu
V metodách vracejících úlohy byste měli před zadáním asynchronní části metody ověřit argumenty a vyvolat všechny odpovídající výjimky, jako jsou ArgumentException a ArgumentNullException. Výjimky, které jsou vyvolány v asynchronní části metody, jsou uloženy ve vrácené úloze a neobjeví se, dokud například úloha nebude očekávána. Další informace naleznete v tématu Výjimky v metodách vracejících úlohy.
Vlastní typy výjimek
Následující osvědčené postupy se týkají vlastních typů výjimek:
-
Názvy tříd výjimek mají končit pomocí
Exception
- Zahrnout tři konstruktory
- podle potřeby zadejte další vlastnosti
Ukončete 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 třídy Exception. Napří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á textovou zprávu a vnitřní výjimku.
Příklad najdete v tématu Postupy: Vytvář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 vlastnost FileName.