Domanda

È sconsigliato catturare semplicemente System.Exception . Invece, solo il "noto" le eccezioni dovrebbero essere colte.

Ora, questo a volte porta a un codice ripetitivo non necessario, ad esempio:

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

Mi chiedo: c'è un modo per catturare entrambe le eccezioni e chiamare una volta WebId = Guid.Empty ?

L'esempio dato è piuttosto semplice, poiché è solo un GUID . Ma immagina il codice in cui modifichi più volte un oggetto e se una delle manipolazioni fallisce in modo previsto, vuoi "resettare" oggetto . Tuttavia, se c'è un'eccezione imprevista, voglio ancora lanciarlo più in alto.

È stato utile?

Soluzione

Cattura System.Exception e attiva i tipi

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

    throw;
}

Altri suggerimenti

EDIT: concordo con gli altri che stanno dicendo che, a partire da C # 6.0, i filtri delle eccezioni sono ora un modo perfetto per andare: cattura (Eccezione ex) quando (ex è ... || ex is ...)

Tranne il fatto che odio ancora il layout a una riga lunga e definirei il codice come segue. Penso che sia tanto funzionale quanto estetico, poiché credo che migliora la comprensione. Alcuni potrebbero non essere d'accordo:

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

ORIGINALE:

So di essere un po 'in ritardo alla festa qui, ma fumo santo ...

Passando direttamente al sodo, questo tipo di duplica una risposta precedente, ma se vuoi davvero eseguire un'azione comune per diversi tipi di eccezioni e mantenere tutto pulito e ordinato nell'ambito del metodo unico, perché non solo usare una funzione lambda / closing / inline per fare qualcosa del genere? Voglio dire, è molto probabile che finirai per capire che vuoi solo rendere quella chiusura un metodo separato che puoi utilizzare ovunque. Ma sarà super facile farlo senza cambiare strutturalmente il resto del codice. Giusto?

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

Non posso fare a meno di chiedermi ( avvertimento: un po 'di ironia / sarcasmo davanti) perché mai fare questo sforzo per sostituire semplicemente quanto segue:

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

... con qualche variazione folle del prossimo odore di codice, intendo esempio, solo per far finta che stai salvando alcune sequenze di tasti.

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

Perché certamente non è automaticamente più leggibile.

Concesso, ho lasciato le tre istanze identiche di / * in un registro, qualunque sia ... * / return; dal primo esempio.

Ma è una specie del mio punto. Avete sentito parlare di funzioni / metodi, giusto? Sul serio. Scrivi una funzione ErrorHandler comune e, come, chiamala da ogni blocco catch.

Se me lo chiedi, il secondo esempio (con le parole chiave if e è ) è entrambi significativamente meno leggibile e contemporaneamente significativamente più soggetto a errori durante la fase di manutenzione del tuo progetto.

La fase di manutenzione, per chiunque sia relativamente nuovo nella programmazione, comprenderà il 98,7% o più della durata complessiva del progetto e il povero schmuck che fa la manutenzione sarà quasi sicuramente qualcuno diverso da te . E c'è un'ottima possibilità che trascorrano il 50% del loro tempo sul lavoro maledicendo il tuo nome.

E ovviamente FxCop ti abbaia e quindi devi anche anche aggiungere un attributo al tuo codice che ha esattamente zip a che fare con il programma in esecuzione, ed è solo lì per dire a FxCop di ignorare un problema che nel 99,9% dei casi è totalmente corretto nella segnalazione. E, scusa, potrei sbagliarmi, ma questo non "ignora". l'attributo finisce effettivamente compilato nella tua app?

Mettere l'intero se su una riga lo renderebbe più leggibile? Io non la penso così. Voglio dire, una volta un altro programmatore ha sostenuto con veemenza che un altro codice su una riga lo avrebbe reso "più veloce". Ma ovviamente era completamente pazzo. Cercare di spiegargli (con una faccia seria - il che era una sfida) come l'interprete o il compilatore avrebbero spezzato quella lunga riga in discrete istruzioni una per riga - sostanzialmente identiche al risultato se fosse andato avanti e ha appena reso il codice leggibile invece di provare a ingannare il compilatore - non ha avuto alcun effetto su di lui. Ma sto divagando.

Quanto in meno è leggibile quando si aggiungono altri tre tipi di eccezione, tra un mese o due? (Risposta: diventa lot meno leggibile).

Uno dei punti principali, in realtà, è che la maggior parte del punto di formattazione del codice sorgente testuale che tutti noi guardiamo ogni giorno è quello di rendere davvero, davvero ovvio agli altri esseri umani cosa sta realmente accadendo quando il il codice viene eseguito. Perché il compilatore trasforma il codice sorgente in qualcosa di completamente diverso e non potrebbe importare di meno del tuo stile di formattazione del codice. Quindi anche tutto on-one-one-line fa schifo.

Sto solo dicendo ...

// super sucks...
catch( Exception ex )
{
    if ( ex is FormatException || ex is OverflowException || ex is ArgumentNullException )
    {
        // write to a log, whatever...
        return;
    }
    throw;
}

Come altri hanno sottolineato, puoi avere un'istruzione if all'interno del blocco catch per determinare cosa sta succedendo. C # 6 supporta i filtri di eccezione, quindi funzionerà quanto segue:

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

Il metodo MyFilter potrebbe quindi assomigliare a questo:

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

In alternativa, tutto ciò può essere fatto in linea (il lato destro dell'istruzione when deve essere solo un'espressione booleana).

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

Questo è diverso dall'uso di un'istruzione if all'interno del blocco catch , l'uso dei filtri di eccezione non scioglierà lo stack.

Puoi scaricare Visual Studio 2015 per controllare questo fuori.

Se si desidera continuare a utilizzare Visual Studio 2013, è possibile installare il seguente pacchetto nuget:

  

Pacchetti di installazione Microsoft.Net.Compilers

Al momento della stesura, questo includerà il supporto per C # 6.

  

Facendo riferimento a questo pacchetto, il progetto verrà creato usando il   versione specifica dei compilatori C # e Visual Basic contenuti nel file   pacchetto, al contrario di qualsiasi versione installata del sistema.

Purtroppo non in C #, poiché per farlo è necessario un filtro di eccezioni e C # non espone quella funzionalità di MSIL. VB.NET ha questa funzionalità, ad es.

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

Quello che potresti fare è usare una funzione anonima per incapsulare il tuo codice di errore e quindi chiamarlo in quei blocchi di cattura specifici:

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

Per completezza, poiché .NET 4.0 il codice può essere riscritto come:

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

TryParse non genera mai eccezioni e restituisce false se il formato è errato, impostando WebId su Guid.Empty .


Da C # 7 puoi evitare di introdurre una variabile su una riga separata:

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

Puoi anche creare metodi per analizzare le tuple di ritorno, che non sono ancora disponibili in .NET Framework dalla versione 4.6:

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

E usali in questo modo:

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

Il prossimo aggiornamento inutile a questa risposta inutile arriva quando la decostruzione dei parametri fuori è implementata in C # 12. :)

Se riesci ad aggiornare la tua applicazione a C # 6 sei fortunato. La nuova versione di C # ha implementato i filtri di eccezione. Quindi puoi scrivere questo:

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

Alcune persone pensano che questo codice sia lo stesso di

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

Ma non lo è. In realtà questa è l'unica nuova funzionalità in C # 6 che non è possibile emulare nelle versioni precedenti. In primo luogo, un rilancio significa più spese generali che saltare il pescato. In secondo luogo, non è semanticamente equivalente. La nuova funzionalità mantiene intatto lo stack durante il debug del codice. Senza questa funzione, il dump dell'arresto anomalo è meno utile o addirittura inutile.

Guarda una una discussione al riguardo su CodePlex . E un esempio che mostra la differenza .

I filtri di eccezione sono ora disponibili in c # 6+. Puoi farlo

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

In C # 7.0+, puoi combinare anche questo con la corrispondenza dei motivi

try
{
   await Task.WaitAll(tasks);
}
catch (Exception ex) when( ex is AggregateException ae )
{
   //do something with members of ae, say ae.InnerExceptions
}

Se non si desidera utilizzare un'istruzione if all'interno degli ambiti catch , in C # 6.0 può utilizzare la Filtri di eccezione sintassi che era già supportata dal CLR nelle versioni di anteprime ma esisteva solo in VB.NET / MSIL :

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

Questo codice rileverà Exception solo quando è InvalidDataException o ArgumentNullException .

In realtà, puoi inserire praticamente qualsiasi condizione all'interno di quella clausola quando :

static int a = 8;

...

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

Nota che a differenza di un'istruzione if all'interno dell'ambito di catch , Filtri di eccezione non può generare Eccezioni e quando lo fanno, o quando la condizione non è true , verrà invece valutata la condizione catch successiva:

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

Output: cattura generale.

Quando c'è più di un true Filtro eccezioni - il primo verrà accettato:

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

Uscita: cattura.

E come puoi vedere nel MSIL il codice non viene tradotto in if , ma in Filtri e Eccezioni non può essere lanciato all'interno delle aree contrassegnate con Filtro 1 e Filtro 2 ma il filtro che lancia Eccezione non riuscirà invece l'ultimo valore di confronto inserito nello stack prima del comando endfilter determinerà l'esito positivo / negativo del filtro ( Catch 1 XOR Catch 2 verrà eseguito di conseguenza):

 Filtri eccezioni MSIL

Inoltre, in particolare Guid ha Guid. TryParse metodo.

La risposta accettata sembra accettabile, tranne per il fatto che CodeAnalysis / FxCop si lamenterà del fatto che è intercettare un tipo di eccezione generale.

Inoltre, sembra che " sia " l'operatore potrebbe ridurre leggermente le prestazioni.

CA1800: non eseguire il cast inutilmente dice di "valutare invece di testare il risultato dell'operatore" as "anziché", ma se lo fai, scriverai più codice che se prendi ogni eccezione separatamente.

Comunque, ecco cosa farei:

bool exThrown = false;

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

if (exThrown)
{
    // Something else
}

in C # 6 l'approccio consigliato è usare i filtri di eccezione, ecco un esempio:

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

Con C # 7 la risposta di Michael Stum può essere migliorata mantenendo la leggibilità di un'istruzione switch:

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

Questa è una variante della risposta di Matt (penso che sia un po 'più pulita) ... usa un metodo:

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

    WebId = Guid.Empty;
}

Eventuali altre eccezioni verranno generate e il codice WebId = Guid.Empty; non verrà raggiunto. Se non desideri che altre eccezioni causino l'arresto anomalo del programma, aggiungi questo DOPO le altre due catture:

...
catch (Exception)
{
     // something, if anything
     return; // only need this if you follow the example I gave and put it all in a method
}

La risposta di Joseph Daigle è una buona soluzione, ma ho trovato la seguente struttura sarà un po 'più ordinata e meno soggetta a errori.

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

    // Handle exception
}

Ci sono alcuni vantaggi dell'inversione dell'espressione:

  • Non è necessaria una dichiarazione di reso
  • Il codice non è nidificato
  • Non vi è alcun rischio di dimenticare le dichiarazioni 'throw' o 'return' che nella soluzione di Joseph sono separate dall'espressione.

Può anche essere compattato su una sola riga (anche se non molto carina)

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

    // Handle exception
}

Modifica Il filtro delle eccezioni in C # 6.0 renderà la sintassi un po 'più pulita e verrà fornita con un un numero di altri vantaggi rispetto a qualsiasi soluzione attuale. (in particolare lasciando incolume lo stack)

Ecco come apparirebbe lo stesso problema usando la sintassi di C # 6.0:

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

@Micheal

Versione leggermente rivista del tuo codice:

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

I confronti delle stringhe sono brutti e lenti.

Che ne dici

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

Attenzione e avvertimento: Ancora un altro tipo , stile funzionale.

Ciò che è nel link non risponde direttamente alla tua domanda, ma è banale estenderla per assomigliare a:

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

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

(Fondamentalmente fornire un altro sovraccarico Catch vuoto che ritorna da solo)

La domanda più grande è perché . Non credo che il costo superi il guadagno qui :)

catch (Exception ex)
{
    if (!(
        ex is FormatException ||
        ex is OverflowException))
    {
        throw;
    }
    Console.WriteLine("Hello");
}

Aggiornamento 2015-12-15: vedi https://stackoverflow.com/a/22864936/1718702 per C # 6. È più pulito e ora standard nella lingua.

Pensato per le persone che desiderano una soluzione più elegante per catturare una volta e filtrare le eccezioni, utilizzo un metodo di estensione come dimostrato qui di seguito.

Avevo già questa estensione nella mia libreria, originariamente scritta per altri scopi, ma funzionava perfettamente per il type controllando le eccezioni. Inoltre, imho, sembra più pulito di un mucchio di istruzioni || . Inoltre, a differenza della risposta accettata, preferisco la gestione esplicita delle eccezioni, quindi ex is ... ha avuto un comportamento indesiderabile in quanto le classi derrivate sono assegnabili a quei tipi parent).

Utilizzo

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

Estensione IsAnyOf.cs (vedi esempio completo di gestione degli errori per le dipendenze)

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

Esempio di gestione completa degli errori (Copia-Incolla nella nuova app Console)

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

Due test di unità NUnit di esempio

Il comportamento corrispondente per i tipi Eccezione è esatto (ad es. un bambino NON È una corrispondenza per nessuno dei suoi tipi parent).

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

Dato che mi è sembrato che queste risposte avessero appena toccato la superficie, ho cercato di scavare un po 'più a fondo.

Quindi quello che vorremmo davvero fare è qualcosa che non si compila, diciamo:

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

Il motivo per cui vogliamo questo è perché non vogliamo che il gestore delle eccezioni rilevi le cose di cui abbiamo bisogno in seguito nel processo. Certo, possiamo prendere un'eccezione e verificare con un 'se' cosa fare, ma siamo sinceri, non lo vogliamo davvero. (FxCop, problemi di debugger, bruttezza)

Quindi perché questo codice non viene compilato - e come possiamo hackerarlo in modo tale da farlo?

Se guardiamo il codice, ciò che vorremmo davvero fare è inoltrare la chiamata. Tuttavia, secondo MS Partition II, i blocchi del gestore di eccezioni IL non funzioneranno in questo modo, il che in questo caso ha senso perché ciò implicherebbe che l'oggetto "eccezione" può avere tipi diversi.

O per scriverlo in codice, chiediamo al compilatore di fare qualcosa del genere (beh non è del tutto corretto, ma è la cosa più vicina possibile immagino):

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

Il motivo per cui questo non verrà compilato è abbastanza ovvio: che tipo e valore avrebbe l'oggetto '$ exception' (che qui sono memorizzati nelle variabili 'e')? Il modo in cui vogliamo che il compilatore gestisca questo è di notare che il tipo di base comune di entrambe le eccezioni è 'Eccezione', usalo per una variabile che contenga entrambe le eccezioni e quindi gestisca solo le due eccezioni che vengono rilevate. Il modo in cui questo viene implementato in IL è come 'filtro', che è disponibile in VB.Net.

Per farlo funzionare in C #, abbiamo bisogno di una variabile temporanea con il tipo di base "Eccezione" corretto. Per controllare il flusso del codice, possiamo aggiungere alcuni rami. Ecco qui:

    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.");

Gli ovvi svantaggi di questo sono che non possiamo ri-lanciare correttamente e, beh, siamo onesti, che è una brutta soluzione. La bruttezza può essere risolta un po 'eseguendo l'eliminazione dei rami, il che rende la soluzione leggermente migliore:

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

Questo lascia solo il 'rilancio'. Perché ciò funzioni, dobbiamo essere in grado di eseguire la gestione all'interno del blocco 'catch' - e l'unico modo per farlo funzionare è catturare un oggetto 'Exception'.

A questo punto, possiamo aggiungere una funzione separata che gestisce i diversi tipi di Eccezioni usando la risoluzione di sovraccarico o per gestire l'eccezione. Entrambi hanno degli svantaggi. Per iniziare, ecco il modo di farlo con una funzione di supporto:

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.");

E l'altra soluzione è catturare l'oggetto Exception e gestirlo di conseguenza. La traduzione più letterale per questo, basata sul contesto sopra è questa:

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

Quindi, per concludere:

  • Se non vogliamo rilanciare, potremmo considerare di catturare le giuste eccezioni e di memorizzarle in un tempo temporaneo.
  • Se il gestore è semplice e vogliamo riutilizzare il codice, la soluzione migliore è probabilmente quella di introdurre una funzione di supporto.
  • Se vogliamo rilanciare, non abbiamo altra scelta che mettere il codice in un gestore catch 'Exception', che romperà FxCop e le eccezioni non rilevate del tuo debugger.

Questo è un problema classico che ogni sviluppatore di C # deve affrontare alla fine.

Fammi dividere la tua domanda in 2 domande. Il primo,

Posso rilevare più eccezioni contemporaneamente?

In breve, no.

Il che porta alla domanda successiva,

Come posso evitare di scrivere codice duplicato dato che non riesco a catturare più tipi di eccezione nello stesso blocco catch ()?

Dato il tuo esempio specifico, in cui il valore di fallback è economico da costruire, mi piace seguire questi passaggi:

  1. Inizializza WebId sul valore di fallback.
  2. Costruisci un nuovo Guid in una variabile temporanea.
  3. Imposta WebId sulla variabile temporanea completamente costruita. Rendi questa la dichiarazione finale del blocco try {}.

Quindi il codice è simile a:

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) {}

Se viene generata un'eccezione, WebId non viene mai impostato sul valore semi-costruito e rimane Guid.Empty.

Se costruire il valore di fallback è costoso e reimpostare un valore è molto più economico, spostare il codice di reset nella sua funzione:

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

Quindi stai ripetendo molto codice all'interno di ogni switch di eccezioni? Sembra che estrarre un metodo sarebbe un'idea divina, vero?

Quindi il tuo codice si riduce a questo:

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

Mi chiedo perché nessuno abbia notato quella duplicazione del codice.

Da C # 6 hai inoltre i filtri di eccezione come già menzionato da altri. Quindi puoi modificare il codice sopra in questo modo:

try { ... }
catch(Exception e) when(e is Exception1 || e is Exception2)
{ 
    Reset(instance); 
}

Volevo aggiungere la mia breve risposta a questa discussione già lunga. Qualcosa che non è stato menzionato è l'ordine di precedenza delle dichiarazioni catch, in particolare è necessario essere consapevoli dell'ambito di ciascun tipo di eccezione che si sta tentando di rilevare.

Ad esempio se utilizzi un " catch-all " eccezione come Eccezione precederà tutte le altre dichiarazioni di cattura e ovviamente otterrai errori del compilatore, tuttavia se invertirai l'ordine potresti incatenare le tue dichiarazioni di cattura (penso che un po 'di anti-pattern) puoi mettere il tipo Eccezione generale in fondo e questo catturerà tutte le eccezioni che non soddisfano un livello superiore nel blocco try..catch:

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

Consiglio vivamente alle persone di leggere questo documento MSDN:

Gerarchia di eccezioni

Forse prova a mantenere semplice il tuo codice, ad esempio inserendo un codice comune in un metodo, come faresti in qualsiasi altra parte del codice che non si trova all'interno di una clausola catch?

per esempio:.

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

// ...

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

Proprio come lo farei, cercare di trovare il modello simple is beautiful

Nota che ho trovato un modo per farlo, ma questo sembra più materiale per The Daily WTF :

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

Vale la pena menzionarlo qui. È possibile rispondere a più combinazioni (errore di eccezione e messaggio di eccezione).

Mi sono imbattuto in uno scenario di utilizzo quando ho provato a trasmettere l'oggetto di controllo in un datagrid, con contenuto come TextBox, TextBlock o CheckBox. In questo caso, l'eccezione restituita era la stessa, ma il messaggio variava.

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
} 

Voglio suggerire la risposta più breve (un altro stile funzionale ):

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

Per questo è necessario creare diversi " Cattura " sovraccarichi del metodo, simile a 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));
    }

e così via quante ne desideri. Ma devi farlo una volta e puoi usarlo in tutti i tuoi progetti (o, se hai creato un pacchetto nuget, potremmo usarlo anche noi).

E implementazione CatchMany:

    [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. Non ho messo controlli null per la semplicità del codice, considera di aggiungere convalide dei parametri.

p.s.2 Se si desidera restituire un valore dal catch, è necessario eseguire gli stessi metodi Catch, ma con return e Func anziché Action nei parametri.

Chiama il tentativo e prendi due volte.

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

È proprio così semplice !!

In c # 6.0, Filtri eccezioni sono miglioramenti per la gestione delle eccezioni

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");
    }
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top