Mehrere Ausnahmen gleichzeitig abfangen?

2140

Es wird davon abgeraten, einfach zu fangen System.Exception. Stattdessen sollten nur die "bekannten" Ausnahmen abgefangen werden.

Dies führt manchmal zu unnötigem sich wiederholendem Code, zum Beispiel:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

Ich frage mich: Gibt es eine Möglichkeit, beide Ausnahmen abzufangen und den WebId = Guid.EmptyAnruf nur einmal anzurufen?

Das gegebene Beispiel ist ziemlich einfach, da es nur ein ist GUID. Stellen Sie sich jedoch Code vor, in dem Sie ein Objekt mehrmals ändern. Wenn eine der Manipulationen erwartungsgemäß fehlschlägt, möchten Sie das Objekt "zurücksetzen" object. Wenn es jedoch eine unerwartete Ausnahme gibt, möchte ich diese trotzdem höher werfen.

Michael Stum
quelle
5
Wenn Sie .net 4 und höher verwenden, bevorzuge ich die aggregierte Ausnahme msdn.microsoft.com/en-us/library/system.aggregateexception.aspx
Bepenfriends
2
Bepenfriends- Da System.Guid keine AggregateException auslöst , wäre es großartig, wenn Sie (oder jemand) eine Antwort posten könnten, die zeigt, wie Sie sie in eine AggregateException usw. einwickeln würden
Wehr
1
Zur Verwendung AggregateException: Auslösen einer AggregateException in meinem eigenen Code
DavidRR
11
"Es wird davon abgeraten, einfach System.Exception abzufangen." -und wenn die Methode 32 Arten von Ausnahmen auslösen kann, was macht man dann? Fang für jeden von ihnen separat schreiben?
Giorgim
5
Wenn eine Methode 32 verschiedene Arten von Ausnahmen auslöst, ist sie schlecht geschrieben. Entweder werden keine Ausnahmen abgefangen, die von eigenen Aufrufen gemacht werden, es wird viel zu viel in einer Methode ausgeführt, oder die Mehrheit / alle dieser 32 sollten eine einzelne Ausnahme mit einem Ursachencode sein.
Flynn1179

Antworten:

2100

Fangen System.Exceptionund schalten Sie die Typen ein

catch (Exception ex)            
{                
    if (ex is FormatException || ex is OverflowException)
    {
        WebId = Guid.Empty;
        return;
    }

    throw;
}
Joseph Daigle
quelle
69
Leider gefällt es FxCop (dh Visual Studio Code Analysis) nicht, wenn Sie eine Ausnahme abfangen.
Andrew Garrison
15
Ich bin damit einverstanden, keine Ausnahme abzufangen, aber in diesem Fall ist der Fang ein Filter. Möglicherweise haben Sie eine höhere Ebene, die andere Ausnahmetypen behandelt. Ich würde sagen, dass dies korrekt ist, obwohl es einen Haken enthält (Ausnahme x). Der Programmablauf wird nicht geändert, es werden nur bestimmte Ausnahmen behandelt, und der Rest der Anwendung kann sich mit anderen Ausnahmetypen befassen.
lkg
28
Die neueste Version von FxCop löst keine Ausnahme aus, wenn der obige Code verwendet wird.
Peter
28
Ich bin mir nicht sicher, was überhaupt mit dem OP-Code falsch war. Die Nummer 1 der akzeptierten Antworten ist fast doppelt so viele Zeilen und weit weniger lesbar.
João Bragança
22
@ JoãoBragança: Während diese Antwort in diesem Beispiel mehr Zeilen verwendet, versuchen Sie sich vorzustellen, ob Sie beispielsweise mit Datei-E / A zu tun haben, und alles, was Sie tun möchten, ist, diese Ausnahmen abzufangen und einige Protokollnachrichten zu erstellen, aber nur die, die Sie von Ihrem erwarten Datei-E / A-Methoden. Dann müssen Sie sich häufig mit einer größeren Anzahl (ungefähr 5 oder mehr) verschiedener Arten von Ausnahmen befassen. In dieser Situation können Sie durch diesen Ansatz einige Zeilen sparen.
Xilconic
595

BEARBEITEN: Ich stimme anderen zu, die sagen, dass Ausnahmefilter ab C # 6.0 jetzt ein perfekter Weg sind:catch (Exception ex) when (ex is ... || ex is ... )

Abgesehen davon, dass ich das einzeilige Layout immer noch hasse und den Code persönlich wie folgt auslegen würde. Ich denke, dies ist ebenso funktional wie ästhetisch, da ich glaube, dass es das Verständnis verbessert. Einige mögen anderer Meinung sein:

catch (Exception ex) when (
    ex is ...
    || ex is ...
    || ex is ...
)

ORIGINAL:

Ich weiß, ich bin ein bisschen zu spät zur Party hier, aber heiliger Rauch ...

Auf den Punkt gebracht, dupliziert diese Art eine frühere Antwort. Wenn Sie jedoch wirklich eine gemeinsame Aktion für mehrere Ausnahmetypen ausführen und das Ganze im Rahmen der einen Methode ordentlich und ordentlich halten möchten, verwenden Sie doch einfach ein Lambda / Closure / Inline-Funktion, um Folgendes zu tun? Ich meine, die Chancen stehen gut, dass Sie am Ende erkennen, dass Sie diesen Verschluss nur zu einer separaten Methode machen möchten, die Sie überall anwenden können. Aber dann wird es super einfach sein, dies zu tun, ohne den Rest des Codes strukturell zu ändern. Recht?

private void TestMethod ()
{
    Action<Exception> errorHandler = ( ex ) => {
        // write to a log, whatever...
    };

    try
    {
        // try some stuff
    }
    catch ( FormatException  ex ) { errorHandler ( ex ); }
    catch ( OverflowException ex ) { errorHandler ( ex ); }
    catch ( ArgumentNullException ex ) { errorHandler ( ex ); }
}

Ich kann nicht anders, als mich zu fragen ( Warnung: ein wenig Ironie / Sarkasmus voraus), warum um alles in der Welt all diese Anstrengungen unternommen werden, um im Grunde nur Folgendes zu ersetzen:

try
{
    // try some stuff
}
catch( FormatException ex ){}
catch( OverflowException ex ){}
catch( ArgumentNullException ex ){}

... mit einer verrückten Variation dieses nächsten Codegeruchs, meine ich zum Beispiel, nur um so zu tun, als würden Sie ein paar Tastenanschläge speichern.

// sorta sucks, let's be honest...
try
{
    // try some stuff
}
catch( Exception ex )
{
    if (ex is FormatException ||
        ex is OverflowException ||
        ex is ArgumentNullException)
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

Weil es sicherlich nicht automatisch besser lesbar ist.

Zugegeben, ich habe die drei identischen Instanzen /* write to a log, whatever... */ return;aus dem ersten Beispiel herausgelassen.

Aber das ist irgendwie mein Punkt. Sie haben schon von Funktionen / Methoden gehört, oder? Ernsthaft. Schreiben Sie eine gemeinsame ErrorHandlerFunktion und rufen Sie sie von jedem catch-Block aus auf.

Wenn Sie mich fragen, ist das zweite Beispiel (mit den Schlüsselwörtern ifund is) sowohl deutlich weniger lesbar als auch gleichzeitig während der Wartungsphase Ihres Projekts wesentlich fehleranfälliger.

Die Wartungsphase für alle, die noch relativ neu in der Programmierung sind, wird 98,7% oder mehr der gesamten Lebensdauer Ihres Projekts ausmachen, und der arme Trottel, der die Wartung durchführt, wird mit ziemlicher Sicherheit jemand anderes als Sie sein. Und es besteht eine sehr gute Chance, dass sie 50% ihrer Zeit damit verbringen, Ihren Namen zu verfluchen.

Und natürlich bellt FxCop Sie an, und deshalb müssen Sie Ihrem Code auch ein Attribut hinzufügen, das genau mit dem laufenden Programm zu tun hat und nur dazu dient, FxCop anzuweisen, ein Problem zu ignorieren, das in 99,9% der Fälle vollständig auftritt richtig in der Kennzeichnung. Tut mir leid, ich könnte mich irren, aber wird dieses Attribut "Ignorieren" nicht tatsächlich in Ihre App kompiliert?

Würde ifes besser lesbar sein , den gesamten Test in eine Zeile zu setzen? Das glaube ich nicht. Ich meine, ich hatte vor langer Zeit einen anderen Programmierer, der vehement argumentierte, dass mehr Code in eine Zeile "schneller laufen" würde. Aber natürlich war er total verrückt. Der Versuch, ihm (mit ernstem Gesicht - was eine Herausforderung war) zu erklären, wie der Interpreter oder Compiler diese lange Zeile in diskrete Anweisungen mit einer Anweisung pro Zeile zerlegen würde - im Wesentlichen identisch mit dem Ergebnis, wenn er fortgefahren wäre und Ich habe nur den Code lesbar gemacht, anstatt zu versuchen, den Compiler zu überlisten - hatte keinerlei Auswirkungen auf ihn. Aber ich schweife ab.

Wie viel weniger lesbar wird dies, wenn Sie in ein oder zwei Monaten drei weitere Ausnahmetypen hinzufügen? (Antwort: es wird viel weniger lesbar).

Einer der wichtigsten Punkte ist, dass der größte Teil der Formatierung des Textquellcodes, den wir uns jeden Tag ansehen, darin besteht, anderen Menschen wirklich, wirklich klar zu machen, was tatsächlich passiert, wenn der Code ausgeführt wird. Weil der Compiler den Quellcode in etwas völlig anderes verwandelt und sich nicht weniger um Ihren Code-Formatierungsstil kümmert. All-on-One-Line ist also auch total beschissen.

Ich sage nur ...

// super sucks...
catch( Exception ex )
{
    if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
    {
        // write to a log, whatever...
        return;
    }
    throw;
}
Craig
quelle
36
Als ich zum ersten Mal über diese Frage stolperte, war ich über die akzeptierte Antwort hinweg. Cool kann ich einfach alle Exceptions fangen und den Typ überprüfen. Ich dachte, es würde den Code bereinigen, aber irgendetwas brachte mich dazu, auf die Frage zurückzukommen, und ich las tatsächlich die anderen Antworten auf die Frage. Ich habe eine Weile daran gekaut, aber ich muss dir zustimmen. Es ist besser lesbar und wartbar, eine Funktion zum Austrocknen Ihres Codes zu verwenden, als alles abzufangen, den Typ mit einer Liste zu vergleichen, Code zu verpacken und zu werfen. Vielen Dank, dass Sie zu spät gekommen sind und eine alternative und vernünftige Option (IMO) angeboten haben. +1.
Fehler
8
Die Verwendung einer Fehlerbehandlungsfunktion würde nicht funktionieren, wenn Sie a einschließen möchten throw;. Sie müssten diese Codezeile in jedem Catch-Block wiederholen (offensichtlich nicht das Ende der Welt, aber erwähnenswert, da es sich um Code handelt, der wiederholt werden müsste).
Kad81
5
@ kad81, das stimmt, aber Sie würden trotzdem den Vorteil haben, den Protokollierungs- und Bereinigungscode an einer Stelle zu schreiben und ihn bei Bedarf an einer Stelle zu ändern, ohne die doofe Semantik, den Basisausnahmetyp abzufangen und dann basierend auf zu verzweigen der Ausnahmetyp. Und diese eine zusätzliche throw();Anweisung in jedem Catch-Block ist ein geringer Preis, IMO, und lässt Sie dennoch in der Lage, bei Bedarf zusätzliche ausnahmetypspezifische Bereinigungen durchzuführen.
Craig
2
Hallo @Reitffunk, benutze einfach Func<Exception, MyEnumType>statt Action<Exception>. Das ist Func<T, Result>, mit Resultwobei der Rückgabetyp.
Craig
3
Ich bin hier völlig einverstanden. Ich habe auch die erste Antwort gelesen und dachte, es scheint logisch. Auf eine generische 1 für alle Ausnahmebehandlungsroutinen verschoben. Etwas in mir hat mich innerlich zum Kotzen gebracht ... also habe ich den Code zurückgesetzt. Dann stieß ich auf diese Schönheit! Dies muss die akzeptierte Antwort sein
Conor Gallagher
372

Wie andere bereits betont haben, können Sie eine ifAnweisung in Ihrem catch-Block haben, um festzustellen, was los ist. C # 6 unterstützt Ausnahmefilter, daher funktioniert Folgendes:

try {  }
catch (Exception e) when (MyFilter(e))
{
    
}

Die MyFilterMethode könnte dann ungefähr so ​​aussehen:

private bool MyFilter(Exception e)
{
  return e is ArgumentNullException || e is FormatException;
}

Alternativ kann dies alles inline erfolgen (die rechte Seite der when-Anweisung muss nur ein boolescher Ausdruck sein).

try {  }
catch (Exception e) when (e is ArgumentNullException || e is FormatException)
{
    
}

Dies unterscheidet sich von der Verwendung einer ifAnweisung innerhalb des catchBlocks. Durch die Verwendung von Ausnahmefiltern wird der Stapel nicht abgewickelt.

Sie können Visual Studio 2015 herunterladen , um dies zu überprüfen.

Wenn Sie Visual Studio 2013 weiterhin verwenden möchten, können Sie das folgende Nuget-Paket installieren:

Installationspaket Microsoft.Net.Compilers

Zum Zeitpunkt des Schreibens umfasst dies die Unterstützung für C # 6.

Wenn Sie auf dieses Paket verweisen, wird das Projekt mit der spezifischen Version der im Paket enthaltenen C # - und Visual Basic-Compiler erstellt, im Gegensatz zu jeder vom System installierten Version.

Joe
quelle
3
Ich warte geduldig auf die offizielle Veröffentlichung von 6 ... Ich würde gerne sehen, dass dies in diesem Fall ein Kinderspiel wird.
RubberDuck
@RubberDuck Ich sterbe für den Null-Propagierungsoperator von C # 6. Ich versuche, den Rest meines Teams davon zu überzeugen, dass sich das Risiko einer instabilen Sprache / eines instabilen Compilers lohnt. Viele kleinere Verbesserungen mit großer Wirkung. Ich bin glücklich darüber, als Antwort markiert zu werden, nicht wichtig, solange die Leute erkennen, dass dies möglich ist / ist.
Joe
Recht?! Ich werde mir in naher Zukunft meine Codebasis genauer ansehen. =) Ich weiß, dass der Scheck nicht wichtig ist, aber da die akzeptierte Antwort bald veraltet sein wird, hoffe ich, dass OP zurückkommt, um dies zu überprüfen, um ihm die richtige Sichtbarkeit zu geben.
RubberDuck
Das ist teilweise der Grund, warum ich es noch nicht @Joe vergeben habe. Ich möchte, dass dies sichtbar ist. Aus Gründen der Übersichtlichkeit möchten Sie möglicherweise ein Beispiel für einen Inline-Filter hinzufügen.
RubberDuck
188

Leider nicht in C #, da Sie dafür einen Ausnahmefilter benötigen und C # diese Funktion von MSIL nicht verfügbar macht. VB.NET verfügt jedoch über diese Funktion, z

Catch ex As Exception When TypeOf ex Is FormatException OrElse TypeOf ex Is OverflowException

Sie können eine anonyme Funktion verwenden, um Ihren Fehlercode zu kapseln und ihn dann in den folgenden Catch-Blöcken aufzurufen:

Action onError = () => WebId = Guid.Empty;
try
{
    // something
}
catch (FormatException)
{
    onError();
}
catch (OverflowException)
{
    onError();
}
Greg Beech
quelle
26
Interessante Idee und ein weiteres Beispiel, dass VB.net manchmal einige interessante Vorteile gegenüber C # hat
Michael Stum
47
@ MichaelStum mit dieser Art von Syntax würde ich es kaum als interessant bezeichnen ... Schauder
MarioDS
17
Ausnahmefilter kommen in c # 6! Beachten Sie den Unterschied bei der Verwendung von Filtern zugunsten des erneuten Werfens
Arne Deruwe
@ArneDeruwe Danke für diesen Link! Ich habe gerade einen weiteren wichtigen Grund gelernt, nicht erneut zu werfen: throw e;zerstört Stacktrace und Callstack, throw;zerstört "nur" Callstack (macht Crash-Dumps unbrauchbar!) Ein sehr guter Grund, beides nicht zu verwenden, wenn es vermieden werden kann!
AnorZaken
1
Ab C # 6 sind Ausnahmefilter verfügbar! Schließlich.
Danny
134

Der Vollständigkeit halber kann der Code seit .NET 4.0 wie folgt umgeschrieben werden:

Guid.TryParse(queryString["web"], out WebId);

TryParse löst niemals Ausnahmen aus und gibt false zurück, wenn das Format falsch ist, und setzt WebId auf Guid.Empty.


Seit C # 7 können Sie vermeiden, eine Variable in eine separate Zeile einzufügen:

Guid.TryParse(queryString["web"], out Guid webId);

Sie können auch Methoden zum Parsen von zurückgegebenen Tupeln erstellen, die in .NET Framework ab Version 4.6 noch nicht verfügbar sind:

(bool success, Guid result) TryParseGuid(string input) =>
    (Guid.TryParse(input, out Guid result), result);

Und benutze sie so:

WebId = TryParseGuid(queryString["web"]).result;
// or
var tuple = TryParseGuid(queryString["web"]);
WebId = tuple.success ? tuple.result : DefaultWebId;

Das nächste nutzlose Update dieser nutzlosen Antwort erfolgt, wenn die Dekonstruktion von Out-Parametern in C # 12 implementiert ist. :)

Athari
quelle
19
Genau - präzise, ​​und Sie umgehen die Leistungseinbußen bei der Behandlung der Ausnahme, die schlechte Form der absichtlichen Verwendung von Ausnahmen zur Steuerung des Programmflusses und den weichen Fokus, Ihre Konvertierungslogik ein wenig hier und ein wenig dort zu verteilen .
Craig
9
Ich weiß, was du meintest, komme aber natürlich Guid.TryParsenie zurück Guid.Empty. Wenn die Zeichenfolge ein falsches Format hat, setzt sie den resultAusgabeparameter auf Guid.Empty, gibt jedoch zurück false . Ich erwähne es, weil ich Code gesehen habe, der Dinge im Stil von tut Guid.TryParse(s, out guid); if (guid == Guid.Empty) { /* handle invalid s */ }, was normalerweise falsch ist, wenn es ssich um die Zeichenfolgendarstellung von handeln könnte Guid.Empty.
14
Wow, Sie haben die Frage beantwortet, außer dass es nicht im Sinne der Frage ist. Das größere Problem ist etwas anderes :(
Nawfal
6
Das richtige Muster für die Verwendung von TryParse ähnelt natürlich eher dem if( Guid.TryParse(s, out guid){ /* success! */ } else { /* handle invalid s */ }, was keine Mehrdeutigkeit hinterlässt wie das fehlerhafte Beispiel, bei dem der Eingabewert tatsächlich die Zeichenfolgendarstellung eines Guid sein könnte.
Craig
2
Diese Antwort mag in Bezug auf Guid.Parse zwar richtig sein, hat aber den gesamten Punkt der ursprünglichen Frage verfehlt. Was nichts mit Guid.Parse zu tun hatte, sondern das Abfangen von Exception vs FormatException / OverflowException / etc.
Conor Gallagher
115

Ausnahmefilter sind jetzt in c # 6+ verfügbar. Du kannst tun

try
{
       WebId = new Guid(queryString["web"]);
}
catch (Exception ex) when(ex is FormatException || ex is OverflowException)
{
     WebId = Guid.Empty;
}

In C # 7.0+ können Sie dies auch mit dem Mustervergleich kombinieren

try
{
   await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae &&
                           ae.InnerExceptions.Count > tasks.Count/2)
{
   //More than half of the tasks failed maybe..? 
}
Mat J.
quelle
Diese Methode wird nicht nur bevorzugt, weil sie einfach und klar ist, sondern auch nicht abgewickelt werden muss, wenn die Bedingungen nicht erfüllt sind, was im Vergleich zum erneuten Werfen eine bessere Leistung und Diagnoseinformationen bietet.
Joe
74

Wenn Sie Ihre Anwendung auf C # 6 aktualisieren können, haben Sie Glück. Die neue C # -Version hat Ausnahmefilter implementiert. Sie können also Folgendes schreiben:

catch (Exception ex) when (ex is FormatException || ex is OverflowException) {
    WebId = Guid.Empty;
}

Einige Leute denken, dieser Code ist der gleiche wie

catch (Exception ex) {                
    if (ex is FormatException || ex is OverflowException) {
        WebId = Guid.Empty;
    }
    throw;
}

Aber es ist nicht. Tatsächlich ist dies die einzige neue Funktion in C # 6, die in früheren Versionen nicht emuliert werden kann. Erstens bedeutet ein erneuter Wurf mehr Aufwand als das Überspringen des Fangs. Zweitens ist es nicht semantisch äquivalent. Die neue Funktion behält den Stapel beim Debuggen Ihres Codes bei. Ohne diese Funktion ist der Absturzspeicherauszug weniger nützlich oder sogar nutzlos.

Eine Diskussion hierzu finden Sie auf CodePlex . Und ein Beispiel, das den Unterschied zeigt .

Maniero
quelle
4
Wenn Sie ausnahmslos werfen, bleibt der Stapel erhalten, aber "throw ex" überschreibt ihn.
Ivan
32

Wenn Sie nicht über eine verwenden möchten ifAnweisung innerhalb der catchBereiche, in C# 6.0denen Sie verwenden können Exception FiltersSyntax , die bereits von der CLR in Vorschauen Versionen unterstützt wurde , aber gab es nur in VB.NET/ MSIL:

try
{
    WebId = new Guid(queryString["web"]);
}
catch (Exception exception) when (exception is FormatException || ex is OverflowException)
{
    WebId = Guid.Empty;
}

Dieser Code fängt den Exceptioneinzigen ab, wenn es sich um ein InvalidDataExceptionoder handelt ArgumentNullException.

Tatsächlich können Sie grundsätzlich jede Bedingung in diese whenKlausel einfügen:

static int a = 8;

...

catch (Exception exception) when (exception is InvalidDataException && a == 8)
{
    Console.WriteLine("Catch");
}

Beachten Sie, dass im Gegensatz zu einer ifAnweisung im catchGültigkeitsbereich des Bereichs Exception Filtersnicht geworfen werden Exceptionskann. Wenn dies trueder catchFall ist oder wenn die Bedingung nicht erfüllt ist , wird stattdessen die nächste Bedingung ausgewertet:

static int a = 7;

static int b = 0;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

Ausgabe: Allgemeiner Fang.

Wenn es mehr als eine gibt, wird true Exception Filterdie erste akzeptiert:

static int a = 8;

static int b = 4;

...

try
{
    throw new InvalidDataException();
}
catch (Exception exception) when (exception is InvalidDataException && a / b == 2)
{
    Console.WriteLine("Catch");
}
catch (Exception exception) when (exception is InvalidDataException || exception is ArgumentException)
{
    Console.WriteLine("General catch");
}

Ausgabe: Fang.

Und wie Sie im MSILCode sehen können, wird der Code nicht in ifAnweisungen übersetzt , sondern in Filtersund Exceptionskann nicht aus den mit Filter 1und gekennzeichneten Bereichen Filter 2geworfen Exceptionwerden, aber der Filter, der den Code auslöst , schlägt stattdessen fehl, auch der letzte Vergleichswert, der vor dem endfilterBefehl auf den Stapel verschoben wurde bestimmt den Erfolg / Misserfolg des Filters ( Catch 1 XOR Catch 2 wird entsprechend ausgeführt):

Ausnahmefilter MSIL

Hat auch speziell Guiddie Guid.TryParseMethode.

Tamir Vered
quelle
+1 für die Anzeige mehrerer Wann-Filter und eine Erklärung, was passiert, wenn mehrere Filter verwendet werden.
steven87vt
26

Mit C # 7 kann die Antwort von Michael Stum verbessert werden, während die Lesbarkeit einer switch-Anweisung erhalten bleibt :

catch (Exception ex)
{
    switch (ex)
    {
        case FormatException _:
        case OverflowException _:
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}

Und mit C # 8 als Schalterausdruck:

catch (Exception ex)
{
    WebId = ex switch
    {
        _ when ex is FormatException || ex is OverflowException => Guid.Empty,
        _ => throw ex
    };
}
Fabian
quelle
3
Dies sollte die akzeptierte Antwort ab 2018 IMHO sein.
MemphiZ
6
Die Antwort von Mat J whenist weitaus eleganter / angemessener als ein Schalter.
Rgoliveira
@rgoliveira: Ich stimme zu, dass für den in der Frage gestellten Fall die Antwort von Mat J eleganter und angemessener ist. Es ist jedoch schwer zu lesen, ob Sie je nach Ausnahmetyp unterschiedlichen Code haben, den Sie ausführen möchten, oder ob Sie die Instanz der Ausnahme tatsächlich verwenden möchten. Alle diese Szenarien können mit dieser switch-Anweisung gleich behandelt werden.
Fabian
1
@Fabian "Wenn Sie unterschiedlichen Code haben, den Sie je nach Ausnahmetyp ausführen möchten, oder wenn Sie die Instanz der Ausnahme tatsächlich verwenden möchten", erstellen Sie einfach einen anderen catchBlock, oder Sie müssen ihn trotzdem umwandeln. Nach meiner Erfahrung ist ein throw;in Ihrem catchBlock wahrscheinlich ein Codegeruch.
Rgoliveira
@rgoliveira: Die Verwendung eines Wurfs in einem Catch-Block ist in einigen Fällen in Ordnung, siehe Link . Da die Case - Anweisung tatsächlich verwendet Pattern - Matching Link Sie nicht Guss benötigen , wenn Sie den Ablage Operator ersetzen Link durch einen Variablennamen (der Unterstrich). Versteh mich nicht falsch, ich stimme dir zu, dass Ausnahmefilter eine sauberere Methode sind, aber mehrere Fangblöcke fügen viele geschweifte Klammern hinzu.
Fabian
20

Die akzeptierte Antwort scheint akzeptabel zu sein, außer dass CodeAnalysis / FxCop sich über die Tatsache beschwert, dass ein allgemeiner Ausnahmetyp abgefangen wird.

Es scheint auch, dass der Operator "is" die Leistung geringfügig beeinträchtigen könnte.

CA1800: Cast nicht unnötig sagt, dass "stattdessen das Ergebnis des Operators 'as' getestet werden soll", aber wenn Sie dies tun, schreiben Sie mehr Code, als wenn Sie jede Ausnahme einzeln abfangen.

Jedenfalls würde ich Folgendes tun:

bool exThrown = false;

try
{
    // Something
}
catch (FormatException) {
    exThrown = true;
}
catch (OverflowException) {
    exThrown = true;
}

if (exThrown)
{
    // Something else
}
Matt
quelle
19
Beachten Sie jedoch, dass Sie die Ausnahme nicht erneut auslösen können, ohne den Stack-Trace zu verlieren, wenn Sie dies so tun. (Siehe Michael Stums Kommentar zur akzeptierten Antwort)
René
2
Dieses Muster kann durch Speichern der Ausnahme verbessert werden (bitte entschuldigen Sie die schlechte Formatierung - ich kann nicht herausfinden, wie Code in Kommentare eingefügt wird): Ausnahme ex = null; versuche {// etwas} catch (FormatException e) {ex = e; } catch (OverflowException e) {ex = e; } if (ex! = null) {// etwas anderes und beschäftige dich mit ex}
Jesse Weigert
3
@JesseWeigert: 1. Sie können Backticks verwenden, um einem Textstück eine monofreie Schriftart und einen hellgrauen Hintergrund zu geben. 2. Sie können die ursprüngliche Ausnahme einschließlich der Stapelverfolgung immer noch nicht erneut auslösen .
Oliver
2
@CleverNeologism Obwohl es wahr sein kann, dass die Verwendung des isOperators einen leichten negativen Einfluss auf die Leistung hat, ist es auch wahr, dass ein Ausnahmebehandler nicht der Ort ist, an dem man sich übermäßig Gedanken über die Optimierung der Leistung machen muss. Wenn Ihre App so viel Zeit in Ausnahmebehandlungsroutinen verbringt, dass die Leistungsoptimierung dort einen echten Unterschied in der App-Leistung bewirken würde, gibt es andere Codeprobleme, die Sie sich genauer ansehen sollten. Trotzdem gefällt mir diese Lösung immer noch nicht, weil Sie den Stack-Trace verlieren und die Bereinigung kontextuell aus der catch-Anweisung entfernt wird.
Craig
3
Der isBediener beeinträchtigt die Leistung nur, wenn Sie später eine asOperation ausführen (daher wird die Regel unnötig qualifiziert ). Wenn Sie nur die Besetzung testen, ohne die Besetzung tatsächlich durchführen zu müssen, ist der isOperator genau das, was Sie verwenden möchten.
Saluce
19

In C # 6 wird empfohlen, Ausnahmefilter zu verwenden. Hier ein Beispiel:

 try
 {
      throw new OverflowException();
 }
 catch(Exception e ) when ((e is DivideByZeroException) || (e is OverflowException))
 {
       // this will execute iff e is DividedByZeroEx or OverflowEx
       Console.WriteLine("E");
 }
SHM
quelle
18

Dies ist eine Variante von Matts Antwort (ich denke, das ist ein bisschen sauberer) ... benutze eine Methode:

public void TryCatch(...)
{
    try
    {
       // something
       return;
    }
    catch (FormatException) {}
    catch (OverflowException) {}

    WebId = Guid.Empty;
}

Alle anderen Ausnahmen werden ausgelöst und der Code WebId = Guid.Empty;wird nicht getroffen. Wenn Sie nicht möchten, dass andere Ausnahmen Ihr Programm zum Absturz bringen, fügen Sie dies NACH den beiden anderen Fängen hinzu:

...
catch (Exception)
{
     // something, if anything
     return; // only need this if you follow the example I gave and put it all in a method
}
bsara
quelle
-1 Dies wird ausgeführt WebId = Guid.Emtpy, wenn keine Ausnahme ausgelöst wurde.
September
4
@sepster Ich denke, die return-Anweisung nach "// etwas" ist hier impliziert. Ich mag die Lösung nicht wirklich, aber dies ist eine konstruktive Variante in der Diskussion. +1, um deine Ablehnung rückgängig zu machen :-)
toong
@Sepster toong ist richtig, ich nahm an, wenn Sie eine Rückkehr dorthin wollten, dann würden Sie eine setzen ... Ich habe versucht, meine Antwort allgemein genug zu machen, um sie auf alle Situationen anzuwenden, falls andere mit ähnlichen, aber nicht genauen Fragen davon profitieren würden Gut. Aus gutem Grund habe ich returnmeiner Antwort jedoch ein hinzugefügt . Danke für die Eingabe.
Bsara
18

Joseph Daigles Antwort ist eine gute Lösung, aber ich fand die folgende Struktur etwas aufgeräumter und weniger fehleranfällig.

catch(Exception ex)
{   
    if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

Das Invertieren des Ausdrucks bietet einige Vorteile:

  • Eine return-Anweisung ist nicht erforderlich
  • Der Code ist nicht verschachtelt
  • Es besteht kein Risiko, die 'throw'- oder' return'-Aussagen zu vergessen, die in Josephs Lösung vom Ausdruck getrennt sind.

Es kann sogar zu einer einzigen Zeile komprimiert werden (obwohl nicht sehr hübsch)

catch(Exception ex) { if (!(ex is SomeException || ex is OtherException)) throw;

    // Handle exception
}

Bearbeiten: Die Ausnahmefilterung in C # 6.0 macht die Syntax etwas sauberer und bietet gegenüber jeder aktuellen Lösung eine Reihe weiterer Vorteile . (vor allem den Stapel unversehrt lassen)

So würde das gleiche Problem mit der C # 6.0-Syntax aussehen:

catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}
Stefan T.
quelle
2
+1, das ist die beste Antwort. Es ist besser als die Top-Antwort, vor allem, weil es kein gibt return, obwohl das Umkehren des Zustands auch etwas besser ist.
DCShannon
Daran habe ich gar nicht gedacht. Guter Fang, ich werde es der Liste hinzufügen.
Stefan T
16

@ Michael

Leicht überarbeitete Version Ihres Codes:

catch (Exception ex)
{
   Type exType = ex.GetType();
   if (exType == typeof(System.FormatException) || 
       exType == typeof(System.OverflowException)
   {
       WebId = Guid.Empty;
   } else {
      throw;
   }
}

String-Vergleiche sind hässlich und langsam.

FlySwat
quelle
21
Warum nicht einfach das Schlüsselwort "is" verwenden?
Chris Pietschmann
29
@Michael - Wenn Microsoft beispielsweise eine von FormatException abgeleitete StringTooLongException eingeführt hat, handelt es sich immer noch um eine Formatausnahme, nur um eine bestimmte. Es hängt davon ab, ob Sie die Semantik "Fang genau diesen Ausnahmetyp" oder "Ausnahmen fangen, die bedeuten, dass das Format der Zeichenfolge falsch war" möchten.
Greg Beech
6
@ Michael - Beachten Sie auch, dass "catch (FormatException ex) die letztere Semantik hat, es wird alles abfangen, was von FormatException abgeleitet ist.
Greg Beech
14
@Alex Nr. "Throw" ohne "ex" führt die ursprüngliche Ausnahme, einschließlich der ursprünglichen Stapelverfolgung, nach oben. Durch Hinzufügen von "ex" wird die Stapelverfolgung zurückgesetzt, sodass Sie wirklich eine andere Ausnahme als das Original erhalten. Ich bin sicher, jemand anderes kann es besser erklären als ich. :)
Samantha Branham
13
-1: Dieser Code ist äußerst fragil - eine Bibliothek Entwickler können damit rechnen , zu ersetzen , throw new FormatException();mit throw new NewlyDerivedFromFormatException();ohne Code zu brechen , die Bibliothek verwendet wird , und es wird Fälle mit Ausnahme gilt für alle Ausnahme wahr Umgang mit dem jemand verwendete ==anstelle von is(oder einfach catch (FormatException)).
Sam Harwell
13

Wie wäre es mit

try
{
    WebId = Guid.Empty;
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
Maurice
quelle
Das funktioniert nur, wenn der Catch-Code vollständig in den Try-Block verschoben werden kann. Imaging-Code, bei dem Sie mehrere Manipulationen an einem Objekt vornehmen und eine in der Mitte, schlägt fehl und Sie möchten das Objekt "zurücksetzen".
Michael Stum
4
In diesem Fall würde ich eine Reset-Funktion hinzufügen und diese aus mehreren Catch-Blöcken aufrufen.
Maurice
12
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}
Konstantin Spirin
quelle
11

Vorsicht und Warnung: Noch ein anderer, funktionaler Stil.

Was in dem Link enthalten ist, beantwortet Ihre Frage nicht direkt, aber es ist trivial, sie so zu erweitern, dass sie aussieht:

static void Main() 
{ 
    Action body = () => { ...your code... };

    body.Catch<InvalidOperationException>() 
        .Catch<BadCodeException>() 
        .Catch<AnotherException>(ex => { ...handler... })(); 
}

(Grundsätzlich eine weitere leere CatchÜberladung bereitstellen, die sich selbst zurückgibt)

Die größere Frage dazu ist warum . Ich denke nicht, dass die Kosten den Gewinn hier überwiegen :)

nawfal
quelle
1
Ein möglicher Vorteil dieses Ansatzes besteht darin, dass es einen semantischen Unterschied zwischen dem Abfangen und erneuten Auslösen einer Ausnahme und dem Nichtabfangen einer Ausnahme gibt. In einigen Fällen sollte Code auf eine Ausnahme reagieren, ohne sie abzufangen. So etwas ist in vb.net möglich, aber nicht in C #, es sei denn, man verwendet einen in vb.net geschriebenen und von C # aufgerufenen Wrapper.
Supercat
1
Wie kann man auf eine Ausnahme reagieren, ohne sie zu fangen? Ich verstehe dich nicht ganz.
Nawfal
@nawful ... mit vb Filter - Funktion filt (ex als Ausnahme): LogEx (ex): return false ... dann in der catch-Zeile: catch ex wenn filt (ex)
FastAl
1
@FastAl Ist das nicht das, was Ausnahmefilter in C # 6 erlauben?
HimBromBeere
@HimBromBeere yep sie sind direkte Analoga
FastAl
9

Update 15.12.2015: Siehe https://stackoverflow.com/a/22864936/1718702 C # 6 finden . Es ist sauberer und jetzt Standard in der Sprache.

Ausgerichtet für Menschen, die eine elegantere Lösung suchenIch bin , um einmal zu fangen und Ausnahmen zu filtern. Ich verwende eine Erweiterungsmethode, wie unten gezeigt.

Ich hatte diese Erweiterung bereits in meiner Bibliothek, die ursprünglich für andere Zwecke geschrieben wurde, aber sie funktionierte perfekt type, um Ausnahmen zu überprüfen. Außerdem sieht es imho sauberer aus als eine Reihe von ||Aussagen. Im Gegensatz zur akzeptierten Antwort bevorzuge ich auch die explizite Ausnahmebehandlung, sodass ich ein ex is ...unerwünschtes Verhalten hatte, da abgeleitete Klassen den übergeordneten Typen zugewiesen werden können.

Verwendungszweck

if (ex.GetType().IsAnyOf(
    typeof(FormatException),
    typeof(ArgumentException)))
{
    // Handle
}
else
    throw;

IsAnyOf.cs-Erweiterung (Abhängigkeiten siehe Beispiel für die vollständige Fehlerbehandlung)

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }
    }
}

Beispiel für die vollständige Fehlerbehandlung (Kopieren, Einfügen in neue Konsolen-App)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Common.FluentValidation;

namespace IsAnyOfExceptionHandlerSample
{
    class Program
    {
        static void Main(string[] args)
        {
            // High Level Error Handler (Log and Crash App)
            try
            {
                Foo();
            }
            catch (OutOfMemoryException ex)
            {
                Console.WriteLine("FATAL ERROR! System Crashing. " + ex.Message);
                Console.ReadKey();
            }
        }

        static void Foo()
        {
            // Init
            List<Action<string>> TestActions = new List<Action<string>>()
            {
                (key) => { throw new FormatException(); },
                (key) => { throw new ArgumentException(); },
                (key) => { throw new KeyNotFoundException();},
                (key) => { throw new OutOfMemoryException(); },
            };

            // Run
            foreach (var FooAction in TestActions)
            {
                // Mid-Level Error Handler (Appends Data for Log)
                try
                {
                    // Init
                    var SomeKeyPassedToFoo = "FooParam";

                    // Low-Level Handler (Handle/Log and Keep going)
                    try
                    {
                        FooAction(SomeKeyPassedToFoo);
                    }
                    catch (Exception ex)
                    {
                        if (ex.GetType().IsAnyOf(
                            typeof(FormatException),
                            typeof(ArgumentException)))
                        {
                            // Handle
                            Console.WriteLine("ex was {0}", ex.GetType().Name);
                            Console.ReadKey();
                        }
                        else
                        {
                            // Add some Debug info
                            ex.Data.Add("SomeKeyPassedToFoo", SomeKeyPassedToFoo.ToString());
                            throw;
                        }
                    }
                }
                catch (KeyNotFoundException ex)
                {
                    // Handle differently
                    Console.WriteLine(ex.Message);

                    int Count = 0;
                    if (!Validate.IsAnyNull(ex, ex.Data, ex.Data.Keys))
                        foreach (var Key in ex.Data.Keys)
                            Console.WriteLine(
                                "[{0}][\"{1}\" = {2}]",
                                Count, Key, ex.Data[Key]);

                    Console.ReadKey();
                }
            }
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter matches at least one of the passed in comparisons.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_comparisons">Values to compare against.</param>
        /// <returns>True if a match is found.</returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static bool IsAnyOf<T>(this T p_parameter, params T[] p_comparisons)
        {
            // Validate
            p_parameter
                .CannotBeNull("p_parameter");
            p_comparisons
                .CannotBeNullOrEmpty("p_comparisons");

            // Test for any match
            foreach (var item in p_comparisons)
                if (p_parameter.Equals(item))
                    return true;

            // Return no matches found
            return false;
        }

        /// <summary>
        /// Validates if any passed in parameter is equal to null.
        /// </summary>
        /// <param name="p_parameters">Parameters to test for Null.</param>
        /// <returns>True if one or more parameters are null.</returns>
        public static bool IsAnyNull(params object[] p_parameters)
        {
            p_parameters
                .CannotBeNullOrEmpty("p_parameters");

            foreach (var item in p_parameters)
                if (item == null)
                    return true;

            return false;
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static void CannotBeNull(this object p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw
                    new
                        ArgumentNullException(
                        string.Format("Parameter \"{0}\" cannot be null.",
                        p_name), default(Exception));
        }
    }
}

namespace Common.FluentValidation
{
    public static partial class Validate
    {
        /// <summary>
        /// Validates the passed in parameter is not null or an empty collection, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentOutOfRangeException"></exception>
        public static void CannotBeNullOrEmpty<T>(this ICollection<T> p_parameter, string p_name)
        {
            if (p_parameter == null)
                throw new ArgumentNullException("Collection cannot be null.\r\nParameter_Name: " + p_name, default(Exception));

            if (p_parameter.Count <= 0)
                throw new ArgumentOutOfRangeException("Collection cannot be empty.\r\nParameter_Name: " + p_name, default(Exception));
        }

        /// <summary>
        /// Validates the passed in parameter is not null or empty, throwing a detailed exception message if the test fails.
        /// </summary>
        /// <param name="p_parameter">Parameter to validate.</param>
        /// <param name="p_name">Name of tested parameter to assist with debugging.</param>
        /// <exception cref="ArgumentException"></exception>
        public static void CannotBeNullOrEmpty(this string p_parameter, string p_name)
        {
            if (string.IsNullOrEmpty(p_parameter))
                throw new ArgumentException("String cannot be null or empty.\r\nParameter_Name: " + p_name, default(Exception));
        }
    }
}

Zwei Beispiel-NUnit-Unit-Tests

Das Übereinstimmungsverhalten für ExceptionTypen ist genau (dh ein Kind ist KEINE Übereinstimmung für einen seiner übergeordneten Typen).

using System;
using System.Collections.Generic;
using Common.FluentValidation;
using NUnit.Framework;

namespace UnitTests.Common.Fluent_Validations
{
    [TestFixture]
    public class IsAnyOf_Tests
    {
        [Test, ExpectedException(typeof(ArgumentNullException))]
        public void IsAnyOf_ArgumentNullException_ShouldNotMatch_ArgumentException_Test()
        {
            Action TestMethod = () => { throw new ArgumentNullException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(ArgumentException), /*Note: ArgumentNullException derrived from ArgumentException*/
                    typeof(FormatException),
                    typeof(KeyNotFoundException)))
                {
                    // Handle expected Exceptions
                    return;
                }

                //else throw original
                throw;
            }
        }

        [Test, ExpectedException(typeof(OutOfMemoryException))]
        public void IsAnyOf_OutOfMemoryException_ShouldMatch_OutOfMemoryException_Test()
        {
            Action TestMethod = () => { throw new OutOfMemoryException(); };

            try
            {
                TestMethod();
            }
            catch (Exception ex)
            {
                if (ex.GetType().IsAnyOf(
                    typeof(OutOfMemoryException),
                    typeof(StackOverflowException)))
                    throw;

                /*else... Handle other exception types, typically by logging to file*/
            }
        }
    }
}
HodlDwon
quelle
1
Die Sprache zu verbessern ist nicht "eleganter". An vielen Orten hat dies tatsächlich eine Wartungshölle geschaffen. Jahre später sind viele Programmierer nicht stolz auf das Monster, das sie erschaffen haben. Es ist nicht das, was Sie zum Lesen gewohnt sind. Es kann ein "huh?" Wirkung oder sogar schwere "WTFs". Es ist manchmal verwirrend. Das einzige, was es tut, ist, den Code für diejenigen, die sich später bei der Wartung damit befassen müssen, viel schwieriger zu verstehen - nur weil ein einzelner Programmierer versucht hat, "klug" zu sein. Im Laufe der Jahre habe ich gelernt, dass diese "cleveren" Lösungen selten auch die guten sind.
Kaii
1
oder in wenigen Worten: Halten Sie sich an die Möglichkeiten, die die Sprache von Haus aus bietet. Versuchen Sie nicht, die Semantik einer Sprache zu überschreiben, nur weil Sie sie nicht mögen. Ihre Kollegen (und möglicherweise auch ich) werden Ihnen ehrlich danken.
Kaii
Beachten Sie auch, dass Ihre Lösung whenwie jede Version von nur die Semantik von C # 6 annähert catch (Exception ex) {if (...) {/*handle*/} throw;}. Der wahre Wert von whenist, dass der Filter ausgeführt wird, bevor die Ausnahme abgefangen wird , wodurch die Kosten- / Stapelbeschädigung eines erneuten Auslösens vermieden wird. Es nutzt eine CLR-Funktion, auf die bisher nur VB und MSIL zugreifen konnten.
Marc L.
Eleganter? Dieses Beispiel ist für ein so einfaches Problem so groß und der Code sieht so schrecklich aus, dass es sich nicht einmal lohnt, einen Blick darauf zu werfen. Bitte machen Sie diesen Code nicht zum Problem eines anderen in einem tatsächlichen Projekt.
KthProg
Ihre gesamte IsAnyOfMethode kann einfach umgeschrieben werdenp_comparisons.Contains(p_parameter)
maksymiuk
7

Da ich das Gefühl hatte, dass diese Antworten nur die Oberfläche berührten, versuchte ich etwas tiefer zu graben.

Was wir also wirklich tun möchten, ist etwas, das nicht kompiliert werden kann, sagen wir:

// Won't compile... damn
public static void Main()
{
    try
    {
        throw new ArgumentOutOfRangeException();
    }
    catch (ArgumentOutOfRangeException)
    catch (IndexOutOfRangeException) 
    {
        // ... handle
    }

Der Grund, warum wir dies wollen, ist, dass wir nicht möchten, dass der Ausnahmebehandler Dinge abfängt, die wir später im Prozess benötigen. Sicher, wir können eine Ausnahme abfangen und mit einem "Wenn" prüfen, was zu tun ist, aber seien wir ehrlich, das wollen wir nicht wirklich. (FxCop, Debugger-Probleme, Hässlichkeit)

Warum wird dieser Code nicht kompiliert - und wie können wir ihn so hacken, dass er es tut?

Wenn wir uns den Code ansehen, möchten wir den Anruf wirklich weiterleiten. Gemäß der MS-Partition II funktionieren IL-Ausnahmebehandlungsblöcke jedoch nicht so, was in diesem Fall sinnvoll ist, da dies bedeuten würde, dass das 'Ausnahme'-Objekt unterschiedliche Typen haben kann.

Oder um es in Code zu schreiben, bitten wir den Compiler, so etwas zu tun (nun, es ist nicht ganz richtig, aber es ist das nächstmögliche, was ich denke):

// Won't compile... damn
try
{
    throw new ArgumentOutOfRangeException();
}
catch (ArgumentOutOfRangeException e) {
    goto theOtherHandler;
}
catch (IndexOutOfRangeException e) {
theOtherHandler:
    Console.WriteLine("Handle!");
}

Der Grund, warum dies nicht kompiliert werden kann, liegt auf der Hand: Welchen Typ und Wert hätte das Objekt '$ exception' (die hier in den Variablen 'e' gespeichert sind)? Der Compiler soll damit umgehen, indem er feststellt, dass der gemeinsame Basistyp beider Ausnahmen "Ausnahme" ist. Verwenden Sie diesen Typ für eine Variable, die beide Ausnahmen enthält, und behandeln Sie dann nur die beiden abgefangenen Ausnahmen. Die Art und Weise, wie dies in IL implementiert wird, ist als 'Filter', der in VB.Net verfügbar ist.

Damit es in C # funktioniert, benötigen wir eine temporäre Variable mit dem richtigen Basistyp 'Exception'. Um den Codefluss zu steuern, können wir einige Zweige hinzufügen. Hier geht:

    Exception ex;
    try
    {
        throw new ArgumentException(); // for demo purposes; won't be caught.
        goto noCatch;
    }
    catch (ArgumentOutOfRangeException e) {
        ex = e;
    }
    catch (IndexOutOfRangeException e) {
        ex = e;
    }

    Console.WriteLine("Handle the exception 'ex' here :-)");
    // throw ex ?

noCatch:
    Console.WriteLine("We're done with the exception handling.");

Die offensichtlichen Nachteile dafür sind, dass wir nicht richtig zurückwerfen können und - seien wir ehrlich - dass es eine ziemlich hässliche Lösung ist. Die Hässlichkeit kann ein wenig behoben werden, indem eine Verzweigungseliminierung durchgeführt wird, wodurch die Lösung etwas besser wird:

Exception ex = null;
try
{
    throw new ArgumentException();
}
catch (ArgumentOutOfRangeException e)
{
    ex = e;
}
catch (IndexOutOfRangeException e)
{
    ex = e;
}
if (ex != null)
{
    Console.WriteLine("Handle the exception here :-)");
}

Das lässt nur den "Neuwurf". Damit dies funktioniert, müssen wir in der Lage sein, die Behandlung innerhalb des 'catch'-Blocks durchzuführen - und die einzige Möglichkeit, dies zu erreichen, ist ein abfangendes' Exception'-Objekt.

An dieser Stelle können wir eine separate Funktion hinzufügen, die die verschiedenen Arten von Ausnahmen mithilfe der Überlastungsauflösung oder zur Behandlung der Ausnahme behandelt. Beide haben Nachteile. Hier ist der Weg, um dies mit einer Hilfsfunktion zu tun:

private static bool Handle(Exception e)
{
    Console.WriteLine("Handle the exception here :-)");
    return true; // false will re-throw;
}

public static void Main()
{
    try
    {
        throw new OutOfMemoryException();
    }
    catch (ArgumentException e)
    {
        if (!Handle(e)) { throw; }
    }
    catch (IndexOutOfRangeException e)
    {
        if (!Handle(e)) { throw; }
    }

    Console.WriteLine("We're done with the exception handling.");

Die andere Lösung besteht darin, das Exception-Objekt abzufangen und entsprechend zu behandeln. Die wörtlichste Übersetzung hierfür, basierend auf dem obigen Kontext, lautet:

try
{
    throw new ArgumentException();
}
catch (Exception e)
{
    Exception ex = (Exception)(e as ArgumentException) ?? (e as IndexOutOfRangeException);
    if (ex != null)
    {
        Console.WriteLine("Handle the exception here :-)");
        // throw ?
    }
    else 
    {
        throw;
    }
}

Also zum Schluss:

  • Wenn wir nicht erneut werfen möchten, können wir erwägen, die richtigen Ausnahmen abzufangen und sie vorübergehend zu speichern.
  • Wenn der Handler einfach ist und wir Code wiederverwenden möchten, besteht die beste Lösung wahrscheinlich darin, eine Hilfsfunktion einzuführen.
  • Wenn wir erneut werfen möchten, haben wir keine andere Wahl, als den Code in einen Catch-Handler "Exception" zu setzen, wodurch FxCop und die nicht erfassten Ausnahmen Ihres Debuggers beschädigt werden.
atlaste
quelle
7

Dies ist ein klassisches Problem, mit dem jeder C # -Entwickler irgendwann konfrontiert ist.

Lassen Sie mich Ihre Frage in zwei Fragen aufteilen. Der Erste,

Kann ich mehrere Ausnahmen gleichzeitig abfangen?

Kurz gesagt, nein.

Was zur nächsten Frage führt:

Wie vermeide ich das Schreiben von doppeltem Code, da ich nicht mehrere Ausnahmetypen im selben catch () -Block abfangen kann?

In Anbetracht Ihrer spezifischen Stichprobe, bei der der Fallback-Wert günstig zu konstruieren ist, möchte ich die folgenden Schritte ausführen:

  1. Initialisieren Sie WebId mit dem Fallback-Wert.
  2. Erstellen Sie eine neue Guid in einer temporären Variablen.
  3. Setzen Sie WebId auf die vollständig erstellte temporäre Variable. Machen Sie dies zur endgültigen Anweisung des try {} -Blocks.

Der Code sieht also so aus:

try
{
    WebId = Guid.Empty;
    Guid newGuid = new Guid(queryString["web"]);
    // More initialization code goes here like 
    // newGuid.x = y;
    WebId = newGuid;
}
catch (FormatException) {}
catch (OverflowException) {}

Wenn eine Ausnahme ausgelöst wird, wird WebId niemals auf den halb konstruierten Wert gesetzt und bleibt Guid.Empty.

Wenn das Erstellen des Fallback-Werts teuer und das Zurücksetzen eines Werts viel billiger ist, würde ich den Rücksetzcode in eine eigene Funktion verschieben:

try
{
    WebId = new Guid(queryString["web"]);
    // More initialization code goes here.
}
catch (FormatException) {
    Reset(WebId);
}
catch (OverflowException) {
    Reset(WebId);
}
Jeffrey Rennie
quelle
Dies ist eine nette, "ökologische Codierung", dh Sie denken voraus über Ihren Code- und Daten-Footprint nach und stellen sicher, dass keine halb verarbeiteten Werte verloren gehen. Schön, diesem Muster zu folgen, danke Jeffrey!
Tahir Khalid
6

Sie wiederholen also viel Code in jedem Ausnahmeschalter? Klingt so, als wäre das Extrahieren einer Methode eine gute Idee, nicht wahr?

Ihr Code läuft also auf Folgendes hinaus:

MyClass instance;
try { instance = ... }
catch(Exception1 e) { Reset(instance); }
catch(Exception2 e) { Reset(instance); }
catch(Exception) { throw; }

void Reset(MyClass instance) { /* reset the state of the instance */ }

Ich frage mich, warum niemand diese Codeduplizierung bemerkt hat.

Ab C # 6 haben Sie außerdem die Ausnahmefilter, wie bereits von anderen erwähnt. So können Sie den obigen Code folgendermaßen ändern:

try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{ 
    Reset(instance); 
}
HimBromBeere
quelle
3
"Ich frage mich, warum niemand diese Codeduplizierung bemerkt hat." - UH, was? Der gesamte Punkt der Frage besteht darin, die Codeduplizierung zu beseitigen.
Mark Amery
4

Wollte meine kurze Antwort zu diesem bereits langen Thread hinzufügen. Was nicht erwähnt wurde, ist die Rangfolge der catch-Anweisungen. Insbesondere müssen Sie den Umfang jeder Art von Ausnahme kennen, die Sie abfangen möchten.

Wenn Sie beispielsweise eine "catch-all" -Ausnahme als Ausnahme verwenden , geht sie allen anderen catch-Anweisungen voraus, und Sie erhalten offensichtlich Compilerfehler. Wenn Sie jedoch die Reihenfolge umkehren, können Sie Ihre catch-Anweisungen verketten (ein bisschen ein Anti-Pattern, denke ich ) Sie können den Catch-All- Ausnahmetyp unten einfügen. Dadurch werden alle Ausnahmen erfasst, die in Ihrem try..catch-Block nicht berücksichtigt wurden:

            try
            {
                // do some work here
            }
            catch (WebException ex)
            {
                // catch a web excpetion
            }
            catch (ArgumentException ex)
            {
                // do some stuff
            }
            catch (Exception ex)
            {
                // you should really surface your errors but this is for example only
                throw new Exception("An error occurred: " + ex.Message);
            }

Ich empfehle den Leuten dringend, dieses MSDN-Dokument zu lesen:

Ausnahmehierarchie

Tahir Khalid
quelle
4

Versuchen Sie vielleicht, Ihren Code einfach zu halten, z. B. den allgemeinen Code in eine Methode einzufügen, wie Sie es in jedem anderen Teil des Codes tun würden, der nicht in einer catch-Klausel enthalten ist?

Z.B:

try
{
    // ...
}
catch (FormatException)
{
    DoSomething();
}
catch (OverflowException)
{
    DoSomething();
}

// ...

private void DoSomething()
{
    // ...
}

Wie ich es machen würde, wenn ich versuche, das Einfache zu finden, ist ein schönes Muster

Żubrówka
quelle
3

Beachten Sie, dass ich einen Weg gefunden habe, dies zu tun, aber dies sieht eher nach Material für The Daily WTF aus :

catch (Exception ex)
{
    switch (ex.GetType().Name)
    {
        case "System.FormatException":
        case "System.OverflowException":
            WebId = Guid.Empty;
            break;
        default:
            throw;
    }
}
Michael Stum
quelle
9
-1 Stimme, +5 WTF :-) Dies hätte nicht als Antwort markiert werden sollen, aber es ist großartig.
Aaron
1
Es spielt keine Rolle, wie einfach wir es machen könnten. Aber er saß nicht untätig und kam auf seine Idee, um es zu lösen. Wirklich schätzen.
Maxymus
2
Tun Sie dies jedoch nicht wirklich, verwenden Sie Ausnahmefilter in C # 6 oder eine der anderen Antworten - ich habe dies hier speziell als "Dies ist eine Möglichkeit, aber es ist schlecht und ich möchte etwas besseres tun" angegeben.
Michael Stum
WARUM ist das so schlimm? Ich war verwirrt, dass Sie die Ausnahme nicht direkt in einer switch-Anweisung verwenden konnten.
MKesper
3
@MKesper Ich sehe ein paar Gründe, warum es schlecht ist. Dazu müssen die vollständig qualifizierten Klassennamen als Zeichenfolgenliterale geschrieben werden. Dies ist anfällig für Tippfehler, vor denen der Compiler Sie nicht retten kann. (Dies ist insofern von Bedeutung, als in vielen Geschäften Fehlerfälle weniger gut getestet werden und daher eher geringfügige Fehler übersehen werden.) Außerdem wird eine Ausnahme, die eine Unterklasse eines der angegebenen Fälle ist, nicht berücksichtigt. Und da es sich um Zeichenfolgen handelt, werden die Fälle von Tools wie VS "Alle Referenzen suchen" übersehen - relevant, wenn Sie überall dort, wo eine bestimmte Ausnahme abgefangen wird, einen Bereinigungsschritt hinzufügen möchten.
Mark Amery
2

Erwähnenswert ist hier. Sie können auf mehrere Kombinationen reagieren (Ausnahmefehler und Ausnahmemeldung).

Beim Versuch, ein Steuerobjekt in ein Datagrid mit Inhalten wie TextBox, TextBlock oder CheckBox umzuwandeln, ist ein Anwendungsszenario aufgetreten. In diesem Fall war die zurückgegebene Ausnahme dieselbe, aber die Nachricht variierte.

try
{
 //do something
}
catch (Exception ex) when (ex.Message.Equals("the_error_message1_here"))
{
//do whatever you like
} 
catch (Exception ex) when (ex.Message.Equals("the_error_message2_here"))
{
//do whatever you like
} 
George
quelle
0

Ich möchte die kürzeste Antwort vorschlagen (ein weiterer funktionaler Stil ):

        Catch<FormatException, OverflowException>(() =>
            {
                WebId = new Guid(queryString["web"]);
            },
            exception =>
            {
                WebId = Guid.Empty;
            });

Dazu müssen Sie mehrere "Catch" -Methodenüberladungen erstellen, ähnlich wie bei System.Action:

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2));
    }

    [DebuggerNonUserCode]
    public static void Catch<TException1, TException2, TException3>(Action tryBlock,
        Action<Exception> catchBlock)
    {
        CatchMany(tryBlock, catchBlock, typeof(TException1), typeof(TException2), typeof(TException3));
    }

und so weiter so viele wie du willst. Sie müssen es jedoch einmal ausführen und können es in all Ihren Projekten verwenden (oder wenn Sie ein Nuget-Paket erstellt haben, können wir es auch verwenden).

Und CatchMany-Implementierung:

    [DebuggerNonUserCode]
    public static void CatchMany(Action tryBlock, Action<Exception> catchBlock,
        params Type[] exceptionTypes)
    {
        try
        {
            tryBlock();
        }
        catch (Exception exception)
        {
            if (exceptionTypes.Contains(exception.GetType())) catchBlock(exception);
            else throw;
        }
    }

ps Ich habe keine Nullprüfungen für die Einfachheit des Codes durchgeführt. Erwägen Sie, Parameterüberprüfungen hinzuzufügen.

ps2 Wenn Sie einen Wert aus dem catch zurückgeben möchten, müssen Sie dieselben Catch-Methoden ausführen, jedoch mit return und Func anstelle von Action in Parametern.

Eugene Gorbovoy
quelle
-15

Rufen Sie einfach den Versuch an und fangen Sie zweimal.

try
{
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
    WebId = Guid.Empty;
}
try
{
    WebId = new Guid(queryString["web"]);
}
catch (OverflowException)
{
    WebId = Guid.Empty;
}

So einfach ist das !!

Gelehrter Typ
quelle
3
Äh. Dies vereitelt den Zweck der Frage. Er stellt diese Frage, um doppelten Code loszuwerden. Diese Antwort fügt mehr doppelten Code hinzu.
James Esh
-23

In c # 6.0 sind Ausnahmefilter Verbesserungen für die Ausnahmebehandlung

try
{
    DoSomeHttpRequest();
}
catch (System.Web.HttpException e)
{
    switch (e.GetHttpCode())
    {
        case 400:
            WriteLine("Bad Request");
        case 500:
            WriteLine("Internal Server Error");
        default:
            WriteLine("Generic Error");
    }
}
Kashif
quelle
13
In diesem Beispiel werden keine Ausnahmefilter verwendet.
user247702
Dies ist die Standardmethode zum Filtern von Ausnahmen in c # 6.0
Kashif
5
Schauen Sie sich noch einmal an, was genau Ausnahmefilter sind. In Ihrem Beispiel verwenden Sie keinen Ausnahmefilter. Diese Antwort enthält ein gutes Beispiel , das ein Jahr vor Ihrer veröffentlicht wurde.
user247702
6
Ein Beispiel für die Ausnahmefilterung wärecatch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }
Wert