Frage Ausnahmen fangen mit "fangen, wann"


Ich bin auf dieses neue Feature in C # gestoßen, das die Ausführung eines catch-Handlers ermöglicht, wenn eine bestimmte Bedingung erfüllt ist.

int i = 0;
try
{
    throw new ArgumentNullException(nameof(i));
}
catch (ArgumentNullException e)
when (i == 1)
{
    Console.WriteLine("Caught Argument Null Exception");
}

Ich versuche zu verstehen, wann dies jemals nützlich sein könnte.

Ein Szenario könnte etwa so aussehen:

try
{
    DatabaseUpdate()
}
catch (SQLException e)
when (driver == "MySQL")
{
    //MySQL specific error handling and wrapping up the exception
}
catch (SQLException e)
when (driver == "Oracle")
{
    //Oracle specific error handling and wrapping up of exception
}
..

Aber das ist wieder etwas, das ich innerhalb desselben Handlers tun und an verschiedene Methoden delegieren kann, abhängig vom Typ des Treibers. Macht dies den Code leichter verständlich? Wohl nein.

Ein anderes Szenario, das mir einfällt, ist so etwas wie:

try
{
    SomeOperation();
}
catch(SomeException e)
when (Condition == true)
{
    //some specific error handling that this layer can handle
}
catch (Exception e) //catchall
{
    throw;
}

Auch das ist etwas, was ich tun kann:

try
{
    SomeOperation();
}
catch(SomeException e)
{
    if (condition == true)
    {
        //some specific error handling that this layer can handle
    }
    else
        throw;
}

Wird mit der Funktion 'catch, when' die Ausnahmebehandlung beschleunigt, da der Handler als solcher übersprungen wird und der Stack-Abbau wesentlich früher erfolgen kann als bei der Behandlung der spezifischen Anwendungsfälle innerhalb des Handlers? Gibt es bestimmte Anwendungsfälle, die für diese Funktion besser geeignet sind und die sich dann als gute Praxis eignen?


76
2017-07-21 07:31


Ursprung


Antworten:


Catch-Blöcke erlauben bereits das Filtern nach Art der Ausnahme:

catch (SomeSpecificExceptionType e) {...}

Das when Mit dieser Klausel können Sie diesen Filter auf generische Ausdrücke erweitern.

So, Du benutzt das when Klausel für Fälle, in denen die Art der Ausnahme ist nicht eindeutig genug, um zu bestimmen, ob die Ausnahme hier behandelt werden soll oder nicht.


Ein häufiger Anwendungsfall sind Ausnahmetypen, die eigentlich ein sind Verpackung für mehrere, verschiedene Arten von Fehlern.

Hier ist ein Fall, den ich tatsächlich benutzt habe (in VB, das diese Funktion schon seit einiger Zeit hat):

try
{
    SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
    // Handle the *specific* error I was expecting. 
}

Gleiches für SqlException, die auch eine hat ErrorCode Eigentum. Die Alternative wäre etwa so:

try
{
    SomeLegacyComOperation();
}
catch (COMException e)
{
    if (e.ErrorCode == 0x1234)
    {
        // Handle error
    }
    else
    {
        throw;
    }
}

das ist wohl weniger elegant und unterbricht leicht die Stapelspur.

Darüber hinaus können Sie dasselbe erwähnen Art der Ausnahme zweimal im selben try-catch-block:

try
{
    SomeLegacyComOperation();
}
catch (COMException e) when (e.ErrorCode == 0x1234)
{
    ...
}
catch (COMException e) when (e.ErrorCode == 0x5678)
{
    ...
}

das wäre ohne das nicht möglich when Bedingung.


82
2017-07-21 07:34



Von Roslyns Wiki (Hervorhebung von mir):

Ausnahmefilter sind vorzuziehen, um sie zu fangen und erneut zu starten   Sie Lassen Sie den Stapel unversehrt. Wenn die Ausnahme später den Stapel verursacht   abgeladen werden, können Sie sehen, woher es ursprünglich kam, anstatt   nur der letzte Ort, an dem es wieder aufgetaut wurde.

Es ist auch eine übliche und akzeptierte Form des "Missbrauchs", eine Ausnahme zu verwenden   Filter für Nebenwirkungen; z.B. protokollieren. Sie können Inspizieren Sie eine Ausnahme   "Vorbeifliegen", ohne seinen Kurs abzufangen. In diesen Fällen hat die   Filter ist oft ein Aufruf an eine falsch zurückkehrende Hilfsfunktion, die   führt die Nebenwirkungen aus:

private static bool Log(Exception e) { /* log it */ ; return false; }

… try { … } catch (Exception e) when (Log(e)) { }

Der erste Punkt ist es wert zu demonstrieren.

static class Program
{
    static void Main(string[] args)
    {
        A(1);
    }

    private static void A(int i)
    {
        try
        {
            B(i + 1);
        }
        catch (Exception ex)
        {
            if (ex.Message != "!")
                Console.WriteLine(ex);
            else throw;
        }
    }

    private static void B(int i)
    {
        throw new Exception("!");
    }
}

Wenn wir dies in WinDbg ausführen, bis die Ausnahme ausgelöst wird, und drucken Sie den Stapel mit !clrstack -i -a wir werden sehen, dass nur der Rahmen von A:

003eef10 00a7050d [DEFAULT] Void App.Program.A(I4)

PARAMETERS:
  + int i  = 1

LOCALS:
  + System.Exception ex @ 0x23e3178
  + (Error 0x80004005 retrieving local variable 'local_1')

Wenn wir jedoch das zu verwendende Programm ändern when:

catch (Exception ex) when (ex.Message != "!")
{
    Console.WriteLine(ex);
}

Wir werden sehen, dass der Stapel auch enthält BRahmen:

001af2b4 01fb05aa [DEFAULT] Void App.Program.B(I4)

PARAMETERS:
  + int i  = 2

LOCALS: (none)

001af2c8 01fb04c1 [DEFAULT] Void App.Program.A(I4)

PARAMETERS:
  + int i  = 1

LOCALS:
  + System.Exception ex @ 0x2213178
  + (Error 0x80004005 retrieving local variable 'local_1')

Diese Information kann beim Debuggen von Crash-Dumps sehr nützlich sein.


33
2017-07-21 07:47



Wenn eine Ausnahme ausgelöst wird, identifiziert der erste Durchgang der Ausnahmebehandlung, wo die Ausnahme abgefangen wird Vor den Stapel abwickeln; wenn / wenn der "catch" -Standort identifiziert wird, werden alle "finally" -Blöcke ausgeführt (beachte, dass wenn eine Ausnahme einen "finally" -Block verlässt, die Verarbeitung der früheren Ausnahme abgebrochen werden kann). Sobald das passiert, wird der Code die Ausführung am "Catch" fortsetzen.

Wenn es innerhalb einer Funktion einen Haltepunkt gibt, der als Teil eines "wenn" ausgewertet wird, unterbricht dieser Haltepunkt die Ausführung, bevor ein Stapelabwickeln auftritt. Im Gegensatz dazu wird ein Breakpoint bei einem "Catch" die Ausführung nur anhalten finally Handler haben gelaufen.

Schließlich, wenn Zeilen 23 und 27 von foo Anruf bar, und der Anruf in Zeile 23 löst eine Ausnahme aus, die darin gefangen wird foo und in Zeile 57 erneut auftauchen, dann wird die Stack-Ablaufverfolgung vorschlagen, dass die Ausnahme während des Aufrufs aufgetreten ist bar ab Zeile 57 [Ort des erneuten Durchlaufs], wobei jegliche Information zerstört wird, ob die Ausnahme in dem Anruf der Leitung 23 oder der Leitung 27 aufgetreten ist. Verwenden when Um eine Ausnahme nicht zu vermeiden, werden solche Störungen vermieden.

BTW, ein nützliches Muster, das sowohl in C # als auch in VB.NET unangenehm unangenehm ist, besteht darin, einen Funktionsaufruf innerhalb von a zu verwenden when Klausel, um eine Variable festzulegen, die innerhalb von a verwendet werden kann finally Klausel, um zu bestimmen, ob die Funktion normal beendet wurde, um Fälle zu behandeln, in denen eine Funktion keine Hoffnung hat, eine auftretende Ausnahme "aufzulösen", aber trotzdem darauf basierend Maßnahmen ergreifen muss. Wenn beispielsweise eine Ausnahme in einer Factory-Methode ausgelöst wird, die ein Objekt zurückgeben soll, das Ressourcen einkapselt, müssen alle erworbenen Ressourcen freigegeben werden, aber die zugrunde liegende Ausnahme sollte bis zum Aufrufer weitergeleitet werden. Der sauberste Weg, dies semantisch (wenn auch nicht syntaktisch) zu handhaben, ist a finally Blockieren Sie, ob eine Ausnahmebedingung aufgetreten ist, und geben Sie dann alle Ressourcen frei, die im Namen des Objekts erworben wurden, das nicht mehr zurückgegeben wird. Da Cleanup-Code keine Hoffnung hat, den Zustand zu lösen, der die Ausnahme verursacht hat, sollte dies nicht der Fall sein catch es, aber muss nur wissen, was passiert ist. Aufruf einer Funktion wie:

bool CopySecondArgumentToFirstAndReturnFalse<T>(ref T first, T second)
{
  first = second;
  return false;
}

innerhalb eines when Klausel wird es möglich machen, dass die Fabrikfunktion weiß dass etwas passiert ist.


5
2017-07-21 15:01