Ajánlott eljárások a kivételekhez

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 blocks használata 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étrehozhat egy kivételt, és amikor az alkalmazás helyre tud állni ebből a kivételből, használjon try/catch blokkokat a kód körül. Blokkokban catch mindig rendezze a kivételeket a legelvezetettebbtől a legkevésbé származtatottig. (Minden kivétel az Exception osztályból származik. A származtatottabb kivételeket nem egy catch olyan záradék kezeli, amelyet egy catch alapkivételi osztály 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ávolítsa el az utasításokkal vagy finally blokkokkal using lefoglalt erőforrásokat. A kivételekkel automatikusan törölheti az erőforrásokat az utasítások előnyben using részesítéséhez. Blokkok használatával finally törölheti a nem implementálható IDisposableerőforrásokat. A záradékban lévő finally 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, elkaphatja a kivételt 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

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.

    Feljegyzés

    Az előzetes ellenőrzések az esetek többségében kiküszöbölik a kivételeket. Lehetnek azonban versenyfeltételek, amikor az őrzött állapot megváltozik az ellenőrzés és a művelet között, és ebben az esetben továbbra is kivételt okozhat.

Metódusok meghívása Try* a kivételek elkerülése é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. Ha például az elemezni kívánt érték túl nagy ahhoz, Int32.ParseOverflowException hogy a Int32függvényt képviselje. Ezt a Int32.TryParse kivételt azonban nem veti ki. Ehelyett egy logikai értéket ad vissza, és egy out paraméterrel rendelkezik, amely az elemezt érvényes egész számot tartalmazza a sikerhez. Dictionary<TKey,TValue>.TryGetValue hasonló viselkedéssel rendelkezik egy érték szótárból való lekéréséhez.

A lemondás és az aszinkron kivételek fogása

Jobb, ha aszinkron OperationCanceledException metódust hív meg, ahelyett TaskCanceledException, hogy OperationCanceledExceptionaszinkron metódust hív. Számos aszinkron metódus kivételt OperationCanceledException jelez, ha lemondást kérnek. Ezek a kivételek lehetővé teszik a végrehajtás hatékony leállítását, és a hívásokat a lemondási kérések figyelése után nem lehet feloldani.

Az aszinkron metódusok olyan kivételeket tárolnak, amelyeket a visszaadott feladat végrehajtása során dobnak ki. Ha egy kivételt tárol a visszaadott tevékenységben, akkor a rendszer akkor küldi el a kivételt, amikor a feladatra vár. A használati kivételeket( például ArgumentException) továbbra is szinkron módon dobják ki. 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. Az osztály például olyan metódusokat biztosít, FileStream 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 a fájl végén olvas. 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 legtöbb gyakori hibaesetnél a kivétel kiírása helyett az alapértelmezett értéket adja vissza null . Egy gyakori hibaeset normál vezérlési folyamatnak tekinthető. Ha ezekben az esetekben visszaadja null (vagy alapértelmezés szerint) a teljesítményre gyakorolt hatást, minimalizálja az alkalmazásra gyakorolt hatást.

Értéktípusok esetén fontolja meg, hogy az alkalmazáshoz használandó-e Nullable<T> vagy default hibajelző. A használatával Nullable<Guid>ahelyett , hogy Guid.Emptynull a . default A hozzáadás Nullable<T> néha egyértelműbbé teheti, ha egy érték jelen van vagy hiányzik. Más esetekben a hozzáadás Nullable<T> további eseteket is létrehozhat, 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óknak fel kell tudniuk venni, hogy nincsenek mellékhatások, ha kivételt okoznak egy metódusból. 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 bemutatja az eredeti kivétel újbóli hitelesítésének throw használatát, í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 tulajdonságot InnerException . 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

A kivétel kivédése után az általa hordozott információk egy része a verem nyomkövetése. 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 például az utasításban throw szereplő kivétel megadásával újrakezd egy kivételt, a verem nyomkövetése újraindul az aktuális metódusban, throw eés a metódushívások listája az eredeti metódus között, amely a kivételt eldobta, és az aktuális metódus elveszik. 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őből (catch blokkból) újból meg kell adnia, használja az throw utasítást a kivétel megadása nélkül. A CA2200 kódelemzési szabály 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 (catch blokktól) eltérő helyről szeretné újból létrehozni, akkor a ExceptionDispatchInfo.Capture(Exception) kivétel rögzítéséhez használja a kezelőben, és ExceptionDispatchInfo.Throw() amikor újra meg szeretné oldani. A tulajdonság használatával ExceptionDispatchInfo.SourceException megvizsgálhatja a rögzített kivételt.

Az alábbi példa bemutatja, hogyan használható az 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

Csak akkor vezessen be új kivételosztályt, ha egy előre definiált osztály nem érvényes. Példa:

  • Ha egy tulajdonságkészlet vagy metódushívás nem megfelelő az objektum aktuális állapota miatt, kivételt InvalidOperationException kell kivenni.
  • Ha érvénytelen paramétereket ad át, adjon meg egy kivételt ArgumentException vagy egy előre definiált osztályt, amely a következőből ArgumentExceptionszármazik: .

Feljegyzés

Bár a legjobb, ha lehetőség szerint előre definiált kivételtípusokat használ, bizonyos fenntartott kivételtípusokat nem érdemes létrehozni, példáulAccessViolationException: , IndexOutOfRangeExceptionNullReferenceException ésStackOverflowException. További információ: CA2201: Ne emeljen fenntartott kivételtípusokat.

Kivételszerkesztő 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élda:

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éhány fontos .NET-kivételtípus rendelkezik olyan statikus throw segédmeteként, amely lefoglalja és elveti a kivételt. A megfelelő kivételtípus létrehozása és dobása helyett az alábbi metódusokat kell meghívnia:

Tipp.

Az alábbi kódelemzési szabályok segítenek megtalálni a kódban azokat a helyeket, ahol kihasználhatja ezeket a statikus throw segítőket: CA1510, CA1511, CA1512 és CA1513.

Ha aszinkron metódust implementál, ahelyett, hogy CancellationToken.ThrowIfCancellationRequested() ellenőriznie kell, hogy a törlést kérték-e, majd hozzon létre és dobjon OperationCanceledException. További információ: CA2250.

Honosított sztringüzenet belefoglalása

A felhasználó által látott hibaüzenet a Exception.Message kidobott kivétel tulajdonságából származik, nem pedig a kivételosztály nevéből. Általában úgy rendel hozzá értéket a Exception.Message tulajdonsághoz, hogy átadja az üzenetsztringet egy messagekivételkonstruktor argumentumának.

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:

A megfelelő nyelvhelyesség használata

Írjon egyértelmű mondatokat, és adja meg a befejezési írásjeleket. A tulajdonsághoz Exception.Message rendelt sztring minden mondatának ponttal kell végződnie. Például a "A naplótábla túlcsordult". Helyes nyelvtant és írásjeleket használ.

Dobás utasítások jól

Helyezze el a dobási utasításokat, ahol a verem nyomkövetése hasznos lesz. A verem nyomkövetése azon az utasításon kezdődik, ahol a kivétel ki lesz dobva, és a catch kivételt elkapó utasításnál ér véget.

Ne emelje ki a kivételeket a végső záradékokban

Ne emeljen kivételeket a finally záradékokban. További információ: CA2219 kódelemzési szabály.

Ne emelje ki a váratlan helyek kivételeit

Egyes metódusoknak, például Equalsa , GetHashCodeés ToString metódusoknak, statikus konstruktoroknak és egyenlőségi operátoroknak nem szabad kivételeket kivenni. További információ: CA1065 kódelemzési szabály.

Argumentumérvényesítési kivételek szinkronizálása

A feladat-visszaadó metódusokban ellenőriznie kell az argumentumokat, és ki kell dobnia a megfelelő kivételeket, például ArgumentException és ArgumentNullException, mielőtt beírja a metódus aszinkron részét. A metódus aszinkron részében megjelenő kivételek a visszaadott tevékenységben vannak tárolva, és csak akkor jelennek meg, ha például a feladatra vár. További információ: Kivételek a feladatvisszaküldött metódusokban.

Egyéni kivételtípusok

Az alábbi ajánlott eljárások az egyéni kivételtípusokra vonatkoznak:

Kivételosztálynevek vége a következővel: Exception

Ha egyéni kivételre van szükség, nevezze el megfelelően, és származtassa az Exception osztályból. Példa:

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.

Példa: Útmutató: Felhasználó által definiált kivételek létrehozása.

Szükség szerint adjon meg további tulajdonságokat

Csak akkor adjon meg további tulajdonságokat egy kivételhez (az egyéni üzenetsztring mellett), ha olyan programozott forgatókönyv van, amelyben a további információk hasznosak. Például a FileNotFoundException tulajdonságot adja FileName meg.

Lásd még