wake-up-neo.com

Mehrere Ausnahmen gleichzeitig abfangen?

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

Dies führt nun 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.Empty -Aufruf nur einmal aufzurufen?

Das angegebene Beispiel ist ziemlich einfach, da es sich nur um ein GUID handelt. Stellen Sie sich jedoch Code vor, in dem Sie ein Objekt mehrmals ändern und wenn eine der Manipulationen auf die erwartete Weise fehlschlägt, möchten Sie den object "zurücksetzen". Wenn es jedoch eine unerwartete Ausnahme gibt, möchte ich diese immer noch erhöhen.

1952
Michael Stum

Fang System.Exception und schalte die Typen ein

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

    throw;
}
1968
Joseph Daigle

EDIT: Ich stimme anderen zu, die behaupten, dass ab C # 6.0 Ausnahmefilter eine gute Möglichkeit sind: catch (Exception ex) when (ex is ... || ex is ... )

Abgesehen davon, dass ich das einzeilige Layout immer noch irgendwie hasse und den Code persönlich wie folgt anordnen 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 ...

Wenn Sie jedoch eine gemeinsame Aktion für mehrere Ausnahmetypen ausführen und das Ganze im Rahmen der einen Methode aufgeräumt halten möchten, verwenden Sie einfach ein Lambda/closure/inline-Funktion, um etwas wie das Folgende zu tun? Ich meine, die Chancen stehen gut, dass Sie feststellen, dass Sie diesen Abschluss nur zu einer separaten Methode machen möchten, die Sie überall anwenden können. Aber dann wird es super einfach sein, das zu tun, ohne den Rest des Codes strukturell zu verändern. Richtig?

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 frage mich Warnung: ein wenig Ironie/Sarkasmus voraus, warum um alles in der Welt all diese Anstrengungen unternehmen, um im Grunde nur das Folgende zu ersetzen:

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

... mit einigen verrückten Variationen dieses nächsten Code-Geruchs, ich meine zum Beispiel, nur um vorzutäuschen, dass Sie ein paar Tastenanschläge sparen.

// 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 sicher nicht automatisch lesbarer ist.

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

Aber das ist meine Sache. Sie haben schon von Funktionen/Methoden gehört, oder? Ernsthaft. Schreiben Sie eine allgemeine ErrorHandler -Funktion und rufen Sie sie wie folgt aus jedem catch-Block auf.

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

Die Wartungsphase wird für jeden, der noch relativ neu in der Programmierung ist, 98,7% oder mehr der Gesamtlebensdauer Ihres Projekts ausmachen, und der arme Trottel, der die Wartung durchführt, wird mit ziemlicher Sicherheit jemand anders 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 so müssen Sie auch Ihrem Code ein Attribut hinzufügen, das Hat genau Zip mit dem laufenden Programm zu tun und ist nur dazu da, FxCop anzuweisen, ein Problem zu ignorieren, das in 99,9% der Fälle beim Markieren völlig korrekt ist. Tut mir leid, vielleicht irre ich mich, aber wird dieses Attribut "Ignorieren" nicht tatsächlich in Ihrer App kompiliert?

Würde es besser lesbar sein, den gesamten if -Test in eine Zeile zu setzen? Ich glaube nicht. Ich meine, ich hatte schon vor langer Zeit einen anderen Programmierer, der vehement argumentierte, dass mehr Code in einer Zeile "schneller laufen" würde. Aber natürlich war er total verrückt. Der Versuch, ihm zu erklären (mit ernstem Gesicht - was eine Herausforderung war), wie der Interpreter oder Compiler diese lange Zeile in einzelne Anweisungen pro Zeile aufteilen würde - im Wesentlichen identisch mit dem Ergebnis, wenn er vorgegangen wäre und Ich habe nur den Code lesbar gemacht, anstatt zu versuchen, den Compiler zu überlisten - hatte keinerlei Auswirkung 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 eine Menge weniger lesbar).

Einer der wichtigsten Punkte ist, dass der größte Teil des Formatierens des Quelltextcodes, den wir uns jeden Tag ansehen, darin besteht, anderen Menschen 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 zum Kotzen.

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;
}
489
Craig

Wie bereits erwähnt, können Sie in Ihrem catch-Block eine if -Anweisung verwenden, um festzustellen, was gerade passiert. C # 6 unterstützt Ausnahmefilter, daher funktioniert Folgendes:

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

Die MyFilter -Methode 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 if -Anweisung innerhalb des catch -Blocks, wobei die Verwendung von Ausnahmefiltern den Stapel nicht abwickelt .

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 wird dies die Unterstützung für C # 6 einschließen.

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 allen vom System installierten Versionen.

317
Joe

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

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

Was Sie tun könnten, ist eine anonyme Funktion, um Ihren On-Error-Code zu kapseln und ihn dann in diesen speziellen Catch-Blöcken aufzurufen:

Action onError = () => WebId = Guid.Empty;
try
{
    // something
}
catch (FormatException)
{
    onError();
}
catch (OverflowException)
{
    onError();
}
185
Greg Beech

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 einer separaten 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 zu dieser nutzlosen Antwort kommt, wenn die Dekonstruktion von out-Parametern in C # 12 implementiert wird. :)

129
Athari

Wenn Sie Ihre Anwendung auf C # 6 aktualisieren können, haben Sie Glück. Die neue C # -Version hat Ausnahmefilter implementiert. So können Sie dies 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 Riegels. Zweitens ist es nicht semantisch äquivalent. Die neue Funktion behält den Stapel bei, wenn Sie Ihren Code debuggen. Ohne diese Funktion ist der Absturzspeicherauszug weniger nützlich oder sogar nutzlos.

Siehe ein Diskussion dazu auf CodePlex . Und ein Beispiel zeigt den Unterschied .

72
Maniero

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 der Mustererkennung 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..? 
}
70
Mat J

Wenn Sie keine if -Anweisung innerhalb des catch-Bereichs verwenden möchten, können Sie in C# 6.0 die Exception Filters-Syntax verwenden, das von der CLR bereits in Vorschauversionen unterstützt wurde, aber nur in VB.NET/MSIL vorhanden war:

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

Dieser Code fängt den Exception nur ab, wenn es sich um einen InvalidDataException oder ArgumentNullException handelt.

Tatsächlich können Sie jede Bedingung in diese when -Klausel einfügen:

static int a = 8;

...

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

Beachten Sie, dass Exception Filters im Gegensatz zu einer if -Anweisung im Bereich catch nicht Exceptions auslösen kann, und wenn dies der Fall ist oder wenn die Bedingung nicht true ist, der nächste catch Bedingung wird stattdessen 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 einen trueException Filter gibt, wird der 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");
}

Ausgang: Fang.

Und wie Sie in MSIL sehen können, wird der Code nicht in if Anweisungen übersetzt, sondern in Filters, und Exceptions kann nicht aus den mit Filter 1 und gekennzeichneten Bereichen geworfen werden Filter 2, aber der Filter, der Exception auslöst, schlägt fehl. Außerdem bestimmt der letzte Vergleichswert, der vor dem Befehl endfilter an den Stack gesendet wurde, den Erfolg/Misserfolg des Filters (Catch 1XODERCatch 2 wird entsprechend ausgeführt):

Exception Filters MSIL

Speziell Guid hat auch die Methode Guid.TryParse .

31
Tamir Vered

Mit C # 7 die Antwort von Michael Stum kann 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;
    }
}
20
Fabian

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");
 }
18
SHM

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

Außerdem scheint es, dass der "Ist" -Operator die Leistung geringfügig beeinträchtigt.

CA1800: Nicht unnötig umsetzen sagt, "prüfe stattdessen das Ergebnis des Operators 'as'", aber wenn du Wenn Sie dies tun, schreiben Sie mehr Code als wenn Sie jede Ausnahme einzeln abfangen.

Wie auch immer, hier ist was ich tun würde:

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}
18
Matt

Dies ist eine Variante von Matts Antwort (ich halte das für ein bisschen sauberer) ... benutze eine Methode:

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

    WebId = Guid.Empty;
}

Alle anderen Ausnahmen werden geworfen 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
}
17
bsara

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 hat einige Vorteile:

  • Eine Rückgabeerklärung 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 (wenn auch nicht sehr hübsch)

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

    // Handle exception
}

Edit: Das Ausnahmefilterung in C # 6.0 wird die Syntax ein bisschen klarer machen und mit einem Anzahl von andere Vorteile gegenüber jeder aktuellen Lösung. (vor allem den Stapel unversehrt lassen)

So würde dasselbe Problem bei Verwendung der C # 6.0-Syntax aussehen:

catch(Exception ex) when (ex is SomeException || ex is OtherException)
{
    // Handle exception
}
17
Stefan T

@Micheal

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;
   }
}

Zeichenkettenvergleiche sind hässlich und langsam.

16
FlySwat

Wie wäre es mit

try
{
    WebId = Guid.Empty;
    WebId = new Guid(queryString["web"]);
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
13
Maurice
catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}
12

Vorsicht und Warnung: Noch eine andere Art, funktionaler Stil.

Was in dem Link steht, beantwortet Ihre Frage nicht direkt, aber es ist trivial, es so zu erweitern, dass es so 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 :)

11
nawfal

Update 2015-12-15: Siehe https://stackoverflow.com/a/22864936/1718702 für C # 6. Es ist sauberer und jetzt Standard in der Sprache.

Ausgerichtet auf Leute, die eine elegantere Lösung möchten, um Ausnahmen einmal abzufangen und zu filtern, verwende ich eine Erweiterungsmethode wie unten gezeigt.

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

Verwendung

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

IsAnyOf.cs-Erweiterung (Siehe das vollständige Fehlerbehandlungsbeispiel für Abhängigkeiten)

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;
        }
    }
}

Vollständiges Fehlerbehandlungsbeispiel (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 NUnit-Unit-Tests

Das Übereinstimmungsverhalten für Exception Typen ist genau (dh ein untergeordnetes IS stimmt NICHT mit einem der übergeordneten Typen überein).

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*/
            }
        }
    }
}
9
HodlDwon

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 dafür ist, dass der Exception-Handler keine Dinge abfangen soll, die wir später im Prozess benötigen. Sicher, wir können eine Ausnahme abfangen und mit einem "Wenn" klären, was zu tun ist, aber seien wir ehrlich, das wollen wir nicht wirklich. (FxCop, Debuggerprobleme, 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 implizieren würde, dass das Objekt "Ausnahmebedingung" 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 bestmö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 welchen Wert hätte das Objekt '$ exception' (das hier in den Variablen 'e' gespeichert ist)? Wir möchten, dass der Compiler dies handhabt, indem er feststellt, dass der gemeinsame Basistyp beider Ausnahmen "Ausnahme" ist. Verwenden Sie diesen für eine Variable, die beide Ausnahmen enthält, und behandeln Sie dann nur die beiden Ausnahmen, die abgefangen werden. 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 Fluss des Codes 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 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 Verzweigungsbeseitigung 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 das "Nachwerfen". Damit dies funktioniert, müssen wir in der Lage sein, die Verarbeitung innerhalb des 'catch'-Blocks durchzuführen - und die einzige Möglichkeit, dies zu bewerkstelligen, besteht darin, ein' Exception'-Objekt abzufangen.

An dieser Stelle können wir eine separate Funktion hinzufügen, die die verschiedenen Ausnahmetypen mithilfe der Überladungsauflösung oder zur Behandlung der Ausnahme behandelt. Beide haben Nachteile. Um zu beginnen, ist hier der Weg, um es 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 zu erfassen und entsprechend zu behandeln. Die wörtlichste Übersetzung hierfür, basierend auf dem obigen Kontext, lautet wie folgt:

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 die richtigen Ausnahmen abfangen und sie temporär speichern.
  • Wenn der Handler einfach ist und wir Code wiederverwenden möchten, ist die beste Lösung wahrscheinlich die Einführung einer Hilfsfunktion.
  • Wenn wir erneut werfen wollen, haben wir keine andere Wahl, als den Code in einen 'Exception'-Catch-Handler zu schreiben, der FxCop und die nicht abgefangenen Ausnahmen Ihres Debuggers zerstört.
7
atlaste

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

Lassen Sie mich Ihre Frage in 2 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, folge ich diesen Schritten:

  1. Initialisieren Sie WebId auf den Ersatzwert.
  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 letzten 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 nie auf den halbkonstruierten 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);
}
7
Jeffrey Rennie

Sie wiederholen also jede Menge Code in jedem Ausnahmeschalter? Klingt so, als wäre es eine gute Idee, eine Methode zu extrahieren, nicht wahr?

Ihr Code lautet also:

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 Code-Vervielfältigung bemerkt hat.

Ab C # 6 haben Sie außerdem die Exception-Filter 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); 
}
6
HimBromBeere

Versuchen Sie vielleicht, Ihren Code einfach zu halten, z. B. den allgemeinen Code in eine Methode einzufügen, wie Sie es in einem 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()
{
    // ...
}

Genau so würde ich es machen und versuchen, das simple is beautiful Muster zu finden

4
Żubrówka

Wollte meine kurze Antwort zu diesem schon langen Thread hinzufügen. Was nicht erwähnt wurde, ist die Rangfolge der catch-Anweisungen. Insbesondere müssen Sie sich des Umfangs der einzelnen Ausnahmetypen bewusst sein, die Sie abfangen möchten.

Wenn Sie zum Beispiel eine "catch-all" -Ausnahme als -Ausnahme verwenden, geht sie allen anderen catch-Anweisungen voraus und Sie werden offensichtlich Compiler-Fehler erhalten, wenn Sie dies jedoch rückgängig machen In der Reihenfolge, in der Sie Ihre catch-Anweisungen verketten können (ein bisschen wie ein Anti-Pattern, glaube ich), können Sie den catch-all-Typ Exception am unteren Rand und setzen Hiermit werden alle Ausnahmen erfasst, die in Ihrem try..catch-Block nicht für höhere Werte gesorgt haben:

            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 allen, dieses MSDN-Dokument zu lesen:

Ausnahmehierarchie

4
Tahir Khalid

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;
    }
}
1
Michael Stum

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

Ich bin auf ein Anwendungsszenario gestoßen, als ich versucht habe, ein Steuerobjekt in ein Datagrid mit Inhalten wie TextBox, TextBlock oder CheckBox umzuwandeln. 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
} 
0
George

Ich möchte die kürzeste Antwort vorschlagen (eine weitere 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. Aber Sie müssen es einmal tun und können es in all Ihren Projekten verwenden (oder, wenn Sie ein Nuget-Paket erstellt haben, könnten 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;
        }
    }

p.s. Ich habe keine Nullprüfungen vorgenommen, um den Code zu vereinfachen.

s.2 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 den Parametern.

0
Eugene Gorbovoy