Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
A megfelelő kivételkezelés elengedhetetlen az alkalmazások megbízhatóságához. Szándékosan kezelheti a várt kivételeket, hogy megakadályozza az alkalmazás összeomlását. Az összeomlott alkalmazások azonban megbízhatóbbak és kiszámíthatóbbak, mint a meghatározatlan viselkedésű alkalmazások.
Ez a cikk a kivételek kezelésének és létrehozásának ajánlott eljárásait ismerteti.
Kivételek kezelése
A következő ajánlott eljárások a kivételek kezelésére vonatkoznak:
- try/catch/finally blokkok használata hibák helyreállításához vagy erőforrások felszabadításához
- Gyakori feltételek kezelése a kivételek elkerülése érdekében
- Fogás törlése és aszinkron kivételek
- Tervezési osztályok, hogy elkerülhetők legyenek a kivételek
- Állapot visszaállítása, ha a metódusok kivétel miatt nem fejeződnek be
- Kivételek rögzítése és visszaállítása megfelelően
Használjon try/catch/finally blokkokat a hibákból való helyreállításhoz vagy az erőforrások felszabadításához.
Olyan kód esetén, amely potenciálisan létrehoz egy kivételt, és amikor az alkalmazás helyre tud állni ebből a kivételből, használja try/catch kódblokkokat. A catch blokkokban mindig rendeljen kivételeket a leg származtatottabbtól a legkevésbé származtatottig. (Minden kivétel a Exception osztályból származik. A további származtatott kivételeket nem egy catch záradék kezeli, amelyet egy alapkivételi osztály catch záradéka előz meg.) Ha a kód nem tud helyreállítani egy kivételt, ne kapja meg ezt a kivételt. Ha lehetséges, engedélyezze a metódusok további használatát a hívásverem helyreállításához.
Törölje a using utasításokkal vagy finally blokkokkal lefoglalt erőforrásokat. Részesítse előnyben a using utasításokat az erőforrások automatikus felszabadítására, amikor kivételek lépnek fel. Használjon finally blokkokat az olyan erőforrások eltávolításához, amelyek nem implementálják IDisposable. A finally záradékban lévő kód szinte mindig végrehajtásra kerül, még akkor is, ha kivételeket adnak ki.
Gyakori feltételek kezelése a kivételek elkerülése érdekében
Olyan feltételek esetén, amelyek valószínűleg előfordulnak, de kivételt válthatnak ki, fontolja meg a kivétel elkerülését szolgáló kezelésüket. Ha például egy már lezárt kapcsolatot próbál bezárni, kap egy InvalidOperationException. Ezt elkerülheti, ha egy if utasítással ellenőrzi a kapcsolat állapotát, mielőtt megpróbálná bezárni.
if (conn.State != ConnectionState.Closed)
{
conn.Close();
}
If conn.State <> ConnectionState.Closed Then
conn.Close()
End IF
Ha a lezárás előtt nem ellenőrzi a kapcsolat állapotát, akkor észlelheti a InvalidOperationException kivételt.
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
A választás módszere attól függ, hogy milyen gyakran várható az esemény.
Használjon kivételkezelést, ha az esemény nem gyakran fordul elő, vagyis ha az esemény valóban kivételes, és hibát jelez, például egy váratlan fájlvégzeményt. Kivételkezelés használatakor a rendszer normál körülmények között kevesebb kódot hajt végre.
Ellenőrizze, hogy van-e hibaállapot a kódban, ha az esemény rutinszerűen történik, és a normál végrehajtás része lehet. A gyakori hibafeltételek ellenőrzésekor kevesebb kód lesz végrehajtva, mert elkerüli a kivételeket.
Jegyzet
Az előzetes ellenőrzések az esetek többségében kiküszöbölik a kivételeket. Azonban előfordulhatnak versenyfeltételek, amikor a védett állapot megváltozik az ellenőrzés és a művelet között, és ebben az esetben továbbra is kivételt okozhat.
Hívja meg a Try* metódusokat a kivételek elkerülésének érdekében.
Ha a kivételek teljesítményköltsége tiltott, egyes .NET-kódtár-metódusok alternatív hibakezelési formákat biztosítanak. Például Int32.Parse kivételt dob OverflowException, ha az elemezni kívánt érték túl nagy ahhoz, hogy Int32ábrázolni tudja. Azonban Int32.TryParse nem veti ki ezt a kivételt. Ehelyett egy logikai értéket ad vissza, és van egy out paramétere, amely siker esetén tartalmazza az elemezett érvényes egész számot.
Dictionary<TKey,TValue>.TryGetValue hasonló viselkedéssel rendelkezik, ha egy szótárból próbál értéket lekérni.
A művelet-megszakítások és az aszinkron kivételek kezelése
Jobb, ha egy aszinkron metódus hívásakor a OperationCanceledException-ből származó TaskCanceledExceptionhelyett OperationCanceledException-t fog el. Számos aszinkron metódus megszakítás kérésekor OperationCanceledException kivételt dob. Ezek a kivételek lehetővé teszik a végrehajtás hatékony leállítását, és a hívási verem visszagörgetését, amint egy törlési kérés megfigyelhető.
Az aszinkron metódusok olyan kivételeket tárolnak, amelyeket a visszaadott feladat végrehajtása során dobnak ki. Ha a rendszer kivételt tárol a visszaadott tevékenységben, a rendszer akkor küldi el ezt a kivételt, amikor a tevékenységre vár. A használati kivételek(például ArgumentException) továbbra is szinkron módon jelennek meg. További információ: Aszinkron kivételek.
Osztályokat tervezhet, hogy elkerülhetők legyenek a kivételek
Az osztály olyan metódusokat vagy tulajdonságokat biztosíthat, amelyekkel elkerülheti a kivételt kiváltó hívásokat. A FileStream osztály például olyan metódusokat biztosít, amelyek segítenek megállapítani, hogy a fájl vége el lett-e érve. Ezeket a metódusokat meghívva elkerülheti a kivételt, amely akkor fordul elő, ha túlolvassa a fájl végét. Az alábbi példa bemutatja, hogyan olvasható a fájl végéig kivétel aktiválása nélkül:
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
A kivételek elkerülésének másik módja, ha a leggyakoribb hibaesetek null (vagy alapértelmezett) értékét adja vissza ahelyett, hogy kivételt adnál. Egy gyakori hibaeset normál vezérlési folyamatnak tekinthető. Ha ezekben az esetekben null (vagy alapértelmezett) értéket ad vissza, minimalizálhatja az alkalmazás teljesítményre gyakorolt hatását.
Értéktípusok esetén fontolja meg, hogy Nullable<T> vagy default használja-e az alkalmazás hibajelzőjeként. A Nullable<Guid>használatával defaultnullhelyett Guid.Empty lesz. A Nullable<T> hozzáadása néha egyértelműbbé teheti, ha egy érték jelen van vagy hiányzik. Más esetekben a Nullable<T> hozzáadása további eseteket hozhat létre, amelyek nem szükségesek, és csak a lehetséges hibaforrások létrehozására szolgálnak.
Állapot visszaállítása, ha a metódusok kivétel miatt nem fejeződnek be
A hívók feltételezhetik, hogy nincsenek mellékhatások, ha egy metódusból kivételt dobnak. Ha például olyan kóddal rendelkezik, amely az egyik fiókból való kivonással és egy másik fiókban való befizetéssel utalja át a pénzt, és a befizetés végrehajtása során kivétel történik, nem szeretné, hogy a visszavonás érvényben maradjon.
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
Az előző metódus nem ad közvetlenül kivételeket. A metódust azonban meg kell írnia, hogy a betéti művelet meghiúsulása esetén a visszavonás vissza legyen állítva.
Ennek a helyzetnek az egyik módja az, hogy elkapja a betéti tranzakció által előidézett kivételeket, és visszaállítja a visszavonást.
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
Ez a példa a throw használatát mutatja be az eredeti kivétel újbóli megismétléséhez, így a hívók könnyebben láthatják a probléma valódi okát anélkül, hogy meg kellene vizsgálniuk a InnerException tulajdonságot. Másik lehetőségként új kivételt kell megadni, és az eredeti kivételt belső kivételként kell megadni.
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
Kivételek rögzítése és újbóli visszaállítása
Miután kivétel keletkezik, az általa hordozott információk egy része a veremkivonat. A veremkövetés a metódushívási hierarchia azon hierarchiájának listája, amely azzal a metódussal kezdődik, amely a kivételt eldobja, és a kivételt elkapó metódussal végződik. Ha újradobja a kivételt a throw utasítással, például throw e, akkor a verem nyomkövetése az aktuális metódusnál újraindul, és elveszik a metódushívások listája az eredeti metódus és az aktuális metódus között, amely eldobta a kivételt. Az eredeti verem nyomkövetési adatainak kivétellel való megőrzéséhez két lehetőség közül választhat, amelyek attól függenek, hogy honnan szeretné újból létrehozni a kivételt:
- Ha a kivételt a kivételpéldányt észlelt kezelőn (
catchblokkon) belülről adhatja vissza, használja athrowutasítást a kivétel megadása nélkül. A kódelemzési szabály CA2200 segít megtalálni a kód azon helyeit, ahol véletlenül elveszítheti a verem nyomkövetési adatait. - Ha a kivételt a kezelőtől eltérő helyről (
catchblokkból) szeretné újradobni, a ExceptionDispatchInfo.Capture(Exception) használatával rögzítse a kivételt a kezelőben, és használja a ExceptionDispatchInfo.Throw()-t, amikor újra el akarja dobni. A rögzített kivétel vizsgálatához használhatja a ExceptionDispatchInfo.SourceException tulajdonságot.
Az alábbi példa bemutatja, hogyan használható a ExceptionDispatchInfo osztály, és hogyan nézhet ki a kimenet.
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();
Ha a példakódban szereplő fájl nem létezik, a következő kimenet jön létre:
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
Kivételek kivetése
Az alábbi ajánlott eljárások a kivételek kizárásával kapcsolatosak:
- Előre definiált kivételtípusok használata
- Kivételkészítő metódusok használata
- Honosított üzenetszöveg beillesztése
- Megfelelő nyelvtani használata
- Dobás utasítása jól
- Ne emelje ki a jogerős záradékok kivételeit
- Ne emeljen kivételeket váratlan helyekről
- Argumentumérvényesítési kivételek szinkronizálása
Előre definiált kivételtípusok használata
Csak akkor vezessen be új kivételosztályt, ha egy előre definiált osztály nem érvényes. Például:
- Ha egy tulajdonságkészlet vagy metódushívás nem megfelelő az objektum aktuális állapota miatt, InvalidOperationException kivételt kell alkalmaznia.
- Ha érvénytelen paramétereket adnak át, dobjon egy ArgumentException kivételt vagy a ArgumentException-ből származó előre definiált osztályok egyikét.
Jegyzet
Bár lehetőség szerint célszerű előre definiált kivételtípusokat használni, nem szabad fenntartott kivételtípusokat, például AccessViolationException, IndexOutOfRangeException, NullReferenceException és StackOverflowException. További információ: CA2201: Ne emeljen fenntartott kivételtípusokat.
Kivételépítő metódusok használata
Gyakran előfordul, hogy egy osztály ugyanazt a kivételt dobja ki a megvalósítás különböző pontjairól. A túlzott kód elkerülése érdekében hozzon létre egy segédmetódust, amely létrehozza a kivételt, és visszaadja azt. Például:
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
Bizonyos kulcsfontosságú .NET kivételtípusok rendelkeznek statikus throw segédmetódusokkal, amelyek kezelik és dobják a kivételobjektumot. A megfelelő kivételtípus létrehozása és dobása helyett az alábbi metódusokat kell meghívnia:
- 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
Borravaló
Az alábbi kódelemzési szabályok segítségével megtalálhatja a kód azon helyeit, ahol kihasználhatja ezeket a statikus throw segítőket: CA1510, CA1511, CA1512és CA1513.
Ha egy aszinkron metódust implementál, hívja meg a(z) CancellationToken.ThrowIfCancellationRequested()-t, ahelyett, hogy ellenőrizné, kérték-e a megszüntetést, majd létrehozná és dobná a(z) OperationCanceledException-t. További információ: CA2250.
Honosított karakterláncüzenet tartalmazása
A felhasználó által látott hibaüzenet a kidobott kivétel Exception.Message tulajdonságából származik, nem pedig a kivételosztály nevéből. A Exception.Message tulajdonsághoz általában úgy rendel hozzá értéket, hogy az üzenetsztringet egy message argumentumának továbbítja.
Honosított alkalmazások esetén minden olyan kivételhez meg kell adnia egy honosított üzenetsztringet, amelyet az alkalmazás ki tud dobni. Az erőforrásfájlok segítségével honosított hibaüzeneteket jeleníthet meg. Az alkalmazások honosításával és a honosított sztringek lekérésével kapcsolatos információkért tekintse meg az alábbi cikkeket:
- Útmutató: Felhasználó által definiált kivételek létrehozása honosított kivételüzenetekkel
- Erőforrások a .NET-alkalmazásokban
- System.Resources.ResourceManager
A megfelelő nyelvhelyesség használata
Írjon egyértelmű mondatokat, és adja meg a befejezési írásjeleket. A Exception.Message tulajdonsághoz rendelt karakterlánc minden mondatának ponttal kell végződnie. Például: "A naplótábla túlcsordult." Ez a helyes nyelvtant és írásjeleket használja.
Helyezze el megfelelően a dobás utasításokat
A dobási utasításokat oda helyezze, ahol hasznos a verem nyomkövetése. A verem nyomkövetése azon az utasításon kezdődik, ahol a kivétel ki lesz dobva, és a kivételt elkapó catch utasításra végződik.
Ne emelje ki a kivételeket a végső záradékokban
Ne emelje ki a kivételeket finally záradékokban. További információt a CA2219 kódelemzési szabályában talál.
Ne emelje ki a váratlan helyek kivételeit
Egyes metódusoknak, például Equals, GetHashCodeés ToString metódusoknak, statikus konstruktoroknak és egyenlőségi operátoroknak nem szabad kivételeket kivenni. További információt a CA1065 kódelemzési szabályában talál.
Argumentumérvényesítési kivételek szinkronizálása
A feladatvisszaadó metódusokban az aszinkron rész megadása előtt ellenőriznie kell az argumentumokat, és ki kell dobnia a megfelelő kivételeket, például ArgumentException és ArgumentNullException. A metódus aszinkron részében eldobott kivételek a visszaadott feladatban tárolódnak, és csak akkor jelennek meg, ha például megvárják a feladatot. További információ: Kivételek a feladat-visszaadási metódusokban.
Egyéni kivételtípusok
Az alábbi ajánlott eljárások az egyéni kivételtípusokra vonatkoznak:
-
A kivételosztálynevek végződjenek
Exception - Inkludálj három konstruktort
- Igény szerint adjon meg további tulajdonságokat
Kivételosztályok nevét Exception-val zárja le
Ha egyéni kivételre van szükség, nevezze el megfelelően, és származtassa a Exception osztályból. Például:
public class MyFileNotFoundException : Exception
{
}
Public Class MyFileNotFoundException
Inherits Exception
End Class
Három konstruktor felvétele
A saját kivételosztályok létrehozásakor legalább a három gyakori konstruktort használja: a paraméter nélküli konstruktort, egy sztringüzenetet tartalmazó konstruktort, valamint egy sztringüzenetet és egy belső kivételt tartalmazó konstruktort.
- Exception(), amely alapértelmezett értékeket használ.
- Exception(String), amely egy sztringüzenetet fogad el.
- Exception(String, Exception), amely egy sztringüzenetet és egy belső kivételt fogad el.
Egy példáért lásd: Felhasználó által definiált kivételek létrehozása.
Igény szerint adjon meg további tulajdonságokat
Csak akkor adjon meg további tulajdonságokat egy kivételhez (az egyéni üzenetsztring mellett), ha van olyan programozott forgatókönyv, amelyben a további információk hasznosak. A FileNotFoundException például a FileName tulajdonságot adja meg.