Megosztás a következőn keresztül:


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:

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 (catch blokkon) belülről adhatja vissza, használja a throw utasí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 (catch blokkbó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

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:

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:

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:

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.

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.

Lásd még: