Cattura più eccezioni contemporaneamente?


2140

Si scoraggia semplicemente per catturare System.Exception . Invece, dovrebbero essere rilevate solo le eccezioni "conosciute".

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 la WebId = Guid.Emptychiamata solo una volta?

L'esempio dato è piuttosto semplice, in quanto è 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" il file object. Tuttavia, se c'è un'eccezione inaspettata, voglio ancora lanciarlo più in alto.


5
Se si utilizza .NET 4 e soprattutto io preferisco usare aggregateexception msdn.microsoft.com/en-us/library/system.aggregateexception.aspx
Bepenfriends

2
Bepenfriends- Dal momento che System.Guid non genera AggregateException , sarebbe bello se tu (o qualcuno) potessi pubblicare una risposta che mostri come lo avvolgeresti in AggregateException ecc.
weir


11
"È scoraggiato catturare semplicemente System.Exception." -e se il metodo può generare 32 tipi di eccezioni, cosa si fa? scrivere catch per ciascuno di essi separatamente?
giorgim,

5
Se un metodo genera 32 tipi diversi di eccezioni, è scritto male. O non sta rilevando eccezioni che stanno facendo le proprie chiamate, sta facendo FAR troppo in un metodo, o la maggior parte / tutti quei 32 dovrebbero essere una singola eccezione con un codice motivo.
Flynn1179,

Risposte:


2100

Cattura System.Exceptione attiva i tipi

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

    throw;
}

69
Sfortunatamente, FxCop (ovvero - Visual Studio Code Analysis) non piace quando si rileva l'eccezione.
Andrew Garrison,

15
Concordo con il non catturare l'eccezione, ma, in questo caso, il fermo è un filtro. Potresti avere un livello superiore che gestirà altri tipi di eccezione. Direi che questo è corretto, anche se include una cattura (eccezione x). Non modifica il flusso del programma, gestisce solo determinate eccezioni, quindi consente al resto dell'applicazione di gestire qualsiasi altro tipo di eccezione.
kg

28
L'ultima versione di FxCop non genera un'eccezione quando viene utilizzato il codice sopra.
Peter,

28
In primo luogo, non sono sicuro di cosa sia successo nel codice del PO. La risposta accettata n. 1 è quasi il doppio delle righe e molto meno leggibile.
João Bragança,

22
@ JoãoBragança: Mentre questa risposta in questo esempio utilizza più righe, prova ad immaginare se hai a che fare con il file IO, ad esempio, e tutto ciò che vuoi fare è catturare quelle eccezioni e fare alcuni messaggi di log, ma solo quelli che ti aspetti vengano dal tuo file IO metodi. Quindi spesso devi affrontare un numero maggiore (circa 5 o più) di diversi tipi di eccezioni. In quella situazione, questo approccio può farti risparmiare alcune righe.
Xilconic,

595

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:catch (Exception ex) when (ex is ... || ex is ... )

Tranne il fatto che odio ancora il layout a una riga lunga e che 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 all'inseguimento, questo tipo di duplica una risposta precedente, ma se vuoi davvero eseguire un'azione comune per diversi tipi di eccezione e mantenere tutto pulito e ordinato nell'ambito di un metodo, perché non usare semplicemente un lambda / chiusura / funzione in linea per fare qualcosa di simile al seguente? 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 tutto questo sforzo per sostituire semplicemente quanto segue:

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

... con qualche variazione folle di questo odore di codice successivo, 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 identiche istanze di /* write to a log, whatever... */ return; del primo esempio.

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

Se me lo chiedi, il secondo esempio (con il ifeis parole chiave ) è 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'è una buona probabilità che trascorrano il 50% del loro tempo sul lavoro maledicendo il tuo nome.

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

Metterebbe l'intero if test su una riga lo renderebbe più leggibile? Io non la penso così. Voglio dire, molto tempo fa un altro programmatore sosteneva con veemenza che mettere più codice su una riga lo avrebbe fatto "correre 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 leggibile il codice invece di provare a fare il furbo del compilatore - non ha avuto alcun effetto su di lui. Ma sto divagando.

Quanto meno leggibile si ottiene quando si aggiungono altri tre tipi di eccezione, tra un mese o due? (Risposta: diventa molto 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 ciò che sta realmente accadendo quando 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;
}

36
Quando mi sono imbattuto per la prima volta in questa domanda, ho trovato la risposta accettata. Fantastico posso solo prendere tutti Exceptioni se e controllare il tipo. Ho pensato che avesse ripulito il codice, ma qualcosa mi ha fatto tornare alla domanda e in realtà ho letto le altre risposte alla domanda. L'ho masticato per un po ', ma devo essere d'accordo con te. È più leggibile e gestibile utilizzare una funzione per asciugare il codice piuttosto che catturare tutto, controllare il tipo confrontandolo con un elenco, racchiudendo il codice e gettandolo. Grazie per essere in ritardo e fornire un'opzione alternativa e sana (IMO). +1.
errore

8
L'uso di una funzione di gestione degli errori non funzionerebbe se si desidera includere a throw;. Dovresti ripetere quella riga di codice in ogni blocco catch (ovviamente non è la fine del mondo, ma vale la pena ricordare che è il codice che dovrebbe essere ripetuto).
kad81,

5
@ kad81, è vero, ma avresti comunque il vantaggio di scrivere il codice di registrazione e di pulizia in un posto e, se necessario, cambiarlo in un posto, senza la semantica sciocca di catturare il tipo di eccezione di base quindi ramificare in base a il tipo di eccezione. E quella throw();dichiarazione aggiuntiva in ogni blocco di cattura è un piccolo prezzo da pagare, IMO, e ti lascia comunque nella posizione di fare un'ulteriore pulizia specifica del tipo di eccezione, se necessario.
Craig,

2
Ciao @Reitffunk, basta usare Func<Exception, MyEnumType>invece di Action<Exception>. Cioè Func<T, Result>, Resultessendo il tipo restituito.
Craig

3
Sono completamente d'accordo qui. Anch'io ho letto la prima risposta e il pensiero sembra logico. Spostato in un generico 1 per tutti i gestori di eccezioni. Qualcosa dentro di me mi ha fatto vomitare internamente ... così ho ripristinato il codice. Poi ho scoperto questa bellezza! Questa deve essere la risposta accettata
Conor Gallagher,

372

Come altri hanno sottolineato, puoi avere una ifdichiarazione 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 MyFiltermetodo 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)
{
    
}

Ciò è diverso dall'uso di ifun'istruzione all'interno del catchblocco, l'utilizzo dei filtri delle eccezioni no lo stack.

È possibile scaricare Visual Studio 2015 per verificarlo.

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 utilizzando la versione specifica dei compilatori C # e Visual Basic contenuti nel pacchetto, al contrario di qualsiasi versione installata sul sistema.


3
Aspettando pazientemente l'uscita ufficiale di 6 ... Mi piacerebbe vederlo ottenere il controllo quando ciò accade.
RubberDuck,

@RubberDuck Sto morendo per l'operatore di propagazione null da C # 6. Sto cercando di convincere il resto del mio team che vale la pena il rischio di un linguaggio / compilatore instabile. Molti piccoli miglioramenti con un impatto enorme. Per quanto riguarda l'essere contrassegnato come risposta, non importante, fintanto che le persone si rendono conto che questa volontà / sarà possibile, sono felice.
Joe,

Giusto?! Daremo un'occhiata alla mia base di codice nel prossimo futuro. =) So che il controllo non è importante, ma dato che la risposta accettata sarà presto obsoleta, spero che OP ritorni a controllare questo per dargli la giusta visibilità.
RubberDuck,

Questo è in parte il motivo per cui non l'ho ancora assegnato @Joe. Voglio che questo sia visibile. Tuttavia, potresti voler aggiungere un esempio di un filtro in linea per maggiore chiarezza.
RubberDuck,

188

Purtroppo non in C #, poiché per farlo è necessario un filtro di eccezioni e C # non espone quella funzionalità di MSIL. VB.NET ha questa capacità, 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 catch specifici:

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

26
Un'idea interessante e un altro esempio che VB.net presenta alcuni vantaggi interessanti rispetto a C # a volte
Michael Stum

47
@MichaelStum con quel tipo di sintassi difficilmente lo definirei interessante ... brivido
MarioDS

17
I filtri di eccezione stanno arrivando in c # 6! Notare la differenza nell'uso dei filtri a favore del riprogrammamento roslyn.codeplex.com/discussions/541301
Arne Deruwe,

@ArneDeruwe Grazie per quel link! Ho appena imparato un motivo in più per non rilanciare:throw e; StackTrace distrugge e stack, throw;distrugge "solo" stack (rendering crash-dump inutile!) Una molto buona ragione per usare né se può essere evitato!
AnorZaken,

1
A partire dal C # 6 sono disponibili filtri per le eccezioni! Finalmente.
Danny,

134

Per completezza, da .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. :)


19
Precisamente, conciso e si elude totalmente la penalità prestazionale nel gestire l'eccezione, la cattiva forma di usare intenzionalmente le eccezioni per controllare il flusso del programma e il soft focus di avere la logica di conversione diffusa, un po 'qui e un po' lì .
Craig,

9
So cosa intendevi, ma ovviamente Guid.TryParsenon ritorna mai Guid.Empty. Se la stringa ha un formato errato, imposta il resultparametro di output su Guid.Empty, ma restituisce false . Lo sto citando perché ho visto codice che fa cose nello stile di Guid.TryParse(s, out guid); if (guid == Guid.Empty) { /* handle invalid s */ }, che di solito è sbagliato se spotrebbe essere la rappresentazione di stringhe di Guid.Empty.

14
wow hai risposto alla domanda, tranne per il fatto che non è nello spirito della domanda. Il problema più grande è qualcos'altro :(
nawfal,

6
Il modello corretto per usare TryParse, ovviamente, è più simile if( Guid.TryParse(s, out guid){ /* success! */ } else { /* handle invalid s */ }, il che non lascia ambiguità come nell'esempio spezzato in cui il valore di input potrebbe effettivamente essere la rappresentazione di stringa di un Guid.
Craig,

2
Questa risposta potrebbe effettivamente essere corretta per quanto riguarda Guid.Parse, ma ha perso l'intero punto della domanda originale. Che non aveva nulla a che fare con Guid.Parse, ma riguardava la cattura di Exception vs FormatException / OverflowException / ecc.
Conor Gallagher,

115

I filtri di eccezione sono ora disponibili in c # 6+. Tu puoi fare

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

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

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..? 
}

Questo metodo è preferito non solo perché è semplice e chiaro, ma non deve anche srotolare lo stack se le condizioni non sono soddisfatte, il che fornisce migliori prestazioni e informazioni diagnostiche rispetto al rilancio.
joe

74

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.

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


4
Il tiro senza eccezioni conserva lo stack, ma "lancio ex" lo sovrascriverà.
Ivan,

32

Se non si desidera utilizzare un ifcomunicato all'interno delle catchambiti, in C# 6.0è possibile utilizzare Exception Filtersla sintassi che è stato già sostenuto dal CLR nelle versioni 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 prenderà il Exceptionsolo quando è un InvalidDataExceptionoArgumentNullException .

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

static int a = 8;

...

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

Si noti che a differenza di ifun'istruzione all'interno catchdell'ambito, Exception Filtersnon può essere lanciata Exceptions, e quando lo fanno, o quando la condizione non lo è true, catchverrà invece valutata la condizione 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");
}

Uscita: cattura generale.

Quando ce n'è più di uno true Exception Filter, 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 MSILcodice non viene tradotto in ifistruzioni, ma in Filters, e Exceptionsnon può essere lanciato dall'interno delle aree contrassegnate con Filter 1e Filter 2ma il filtro che lancia Exceptionil endfiltercomando fallirà invece, anche l'ultimo valore di confronto inserito nello stack prima del comando determinerà l'esito positivo / negativo del filtro ( Catch 1 XOR Catch 2 verrà eseguito di conseguenza):

Filtri di eccezione MSIL

Inoltre, Guidha specificamente il Guid.TryParsemetodo.


+1 per mostrare più filtri quando e fornire una spiegazione di ciò che accade quando vengono utilizzati più filtri.
steven87vt,

26

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

E con C # 8 come espressione switch:

catch (Exception ex)
{
    WebId = ex switch
    {
        _ when ex is FormatException || ex is OverflowException => Guid.Empty,
        _ => throw ex
    };
}

3
Questa dovrebbe essere la risposta accettata a partire dall'IMHO 2018.
MemphiZ,

6
L'uso della risposta di Mat J whenè molto più elegante / appropriato di un interruttore.
rgoliveira,

@rgoliveira: sono d'accordo che per il caso posto nella domanda, la risposta di Mat J è più elegante e appropriata. Tuttavia, diventa difficile leggere se si dispone di un codice diverso che si desidera eseguire a seconda del tipo di eccezione o se si desidera effettivamente utilizzare l'istanza dell'eccezione. Tutti questi scenari possono essere trattati allo stesso modo con questa istruzione switch.
Fabian,

1
@Fabian "se si dispone di un codice diverso che si desidera eseguire in base al tipo di eccezione o se si desidera effettivamente utilizzare l'istanza dell'eccezione", si crea semplicemente un catchblocco diverso o è necessario eseguirne il cast comunque .. Nella mia esperienza, un throw;nel tuo catchblocco è probabilmente un odore di codice.
rgoliveira,

@rgoliveira: l'uso di un tiro in un blocco di cattura è OK in diversi casi vedi link . Poiché l'istruzione case utilizza effettivamente il collegamento di corrispondenza del modello , non è necessario eseguire il cast se si sostituisce il collegamento dell'operatore di eliminazione (il trattino basso) con un nome di variabile. Non fraintendetemi, sono d'accordo con voi sul fatto che i filtri di eccezione sono un modo più pulito di farlo, ma più blocchi di cattura aggiungono molte parentesi graffe.
Fabian,

20

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

Inoltre, sembra che l'operatore "is" possa degradare leggermente le prestazioni.

CA1800: non eseguire il cast inutilmente dice di "considerare invece di testare il risultato dell'operatore" as ", 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
}

19
Ma tieni presente che non puoi ripetere l'eccezione senza perdere la traccia dello stack se lo fai in questo modo. (Vedi il commento di Michael Stum alla risposta accettata)
René

2
Questo modello può essere migliorato memorizzando l'eccezione (scusate la scarsa formattazione - non riesco a capire come inserire il codice nei commenti): Eccezione ex = null; prova {// qualcosa} cattura (FormatException e) {ex = e; } catch (OverflowException e) {ex = e; } if (ex! = null) {// qualcos'altro e trattare con l'ex}
Jesse Weigert

3
@JesseWeigert: 1. Puoi usare i backtick per dare a un pezzo di testo un carattere mono-spaziato e uno sfondo grigio chiaro. 2. Non sarà ancora possibile ricodificare l'eccezione originale incluso lo stacktrace .
Oliver,

2
@CleverNeologism anche se può essere vero che l'utilizzo isdell'operatore può avere un leggero impatto negativo sulle prestazioni, è anche vero che un gestore di eccezioni non è il posto in cui preoccuparsi eccessivamente dell'ottimizzazione delle prestazioni. Se la tua app impiega così tanto tempo nei gestori delle eccezioni che l'ottimizzazione delle prestazioni farebbe davvero la differenza nelle prestazioni dell'app, allora ci sono altri problemi di codice da tenere d'occhio. Detto questo, non mi piace ancora questa soluzione perché perdi la traccia dello stack e perché la pulizia viene rimossa contestualmente dall'istruzione catch.
Craig,

3
L'unica volta in cui l' isoperatore degrada le prestazioni è se in seguito esegui asun'operazione (quindi qualificano la regola con inutilmente ). Se tutto ciò che stai facendo è testare il cast senza effettivamente dover eseguire il cast, l' isoperatore è esattamente quello che vuoi usare.
salse

19

in C # 6 l'approccio raccomandato è di 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");
 }

18

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à colpito. 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
}

-1 Questo verrà eseguito WebId = Guid.Emtpynel caso in cui non sia stata generata alcuna eccezione.
Sepster,

4
@sepster Penso che la dichiarazione di ritorno dopo "// qualcosa" sia implicita qui. Non mi piace molto la soluzione, ma questa è una variante costruttiva nella discussione. +1 per annullare le downvote :-)
Toong

@Sepster toong ha ragione, ho pensato che se avessi voluto un ritorno lì, allora ne avresti messo uno ... Stavo cercando di rendere la mia risposta abbastanza generale da applicare a tutte le situazioni nel caso in cui altri con domande simili ma non esatte trarrebbero beneficio come bene. Tuttavia, per buona misura, ho aggiunto un returnalla mia risposta. Grazie per l'input.
bsara,

18

La risposta di Joseph Daigle è una buona soluzione, ma ho trovato la seguente struttura 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 di "lancio" o "ritorno" 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 offre una serie 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
}

2
+1, questa è la risposta migliore. È meglio della risposta principale principalmente perché non c'è return, anche se invertire la condizione è anche un po 'meglio.
DCShannon,

Non ci avevo nemmeno pensato. Buona cattura, lo aggiungerò all'elenco.
Stefan T,

16

@Micheal

Versione leggermente rivista del 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.


21
Perché non usare semplicemente la parola chiave "is"?
Chris Pietschmann,

29
@Michael - Se Microsoft ha introdotto, diciamo, StringTooLongException derivato da FormatException, rimane comunque un'eccezione di formato, solo una specifica. Dipende se si desidera che la semantica di "catturi questo esatto tipo di eccezione" o "catturi le eccezioni che indicano che il formato della stringa era errato".
Greg Beech,

6
@Michael - Inoltre, nota che "catch (FormatException ex) ha quest'ultima semantica, catturerà qualsiasi cosa derivata da FormatException.
Greg Beech

14
@Alex No. "lanciare" senza "ex" porta l'eccezione originale, inclusa la traccia dello stack originale, verso l'alto. L'aggiunta di "ex" ripristina la traccia dello stack, in modo da ottenere davvero un'eccezione diversa dall'originale. Sono sicuro che qualcun altro può spiegarlo meglio di me. :)
Samantha Branham il

13
-1: Questo codice è estremamente fragile - uno sviluppatore libreria poteva aspettarsi di sostituire throw new FormatException();con throw new NewlyDerivedFromFormatException();senza rompere il codice che utilizza la libreria, e si terrà vero per tutti la gestione dei casi, tranne dove qualcuno ha utilizzato eccezione ==al posto di is(o semplicemente catch (FormatException)).
Sam Harwell,

13

Che ne dite di

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

Funziona solo se il Catch-Code può essere completamente spostato nel Try-Block. Ma il codice di imaging in cui si effettuano più manipolazioni su un oggetto e uno nel mezzo fallisce e si desidera "ripristinare" l'oggetto.
Michael Stum

4
In tal caso, aggiungerei una funzione di ripristino e la chiamerei da più blocchi catch.
Maurice,

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

11

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 Catchsovraccarico vuoto che ritorna se stesso)

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


1
Un possibile vantaggio di questo approccio è che esiste una differenza semantica tra la cattura e la riproposizione di un'eccezione rispetto alla non cattura; in alcuni casi, il codice dovrebbe agire su un'eccezione senza rilevarlo. Una cosa del genere è possibile in vb.net, ma non in C # a meno che non si usi un wrapper scritto in vb.net e chiamato da C #.
supercat

1
Come agisce su un'eccezione senza catturarla? Non ti capisco del tutto.
nawfal,

@nawful ... utilizzando il filtro vb - funzione filt (ex come eccezione): LogEx (ex): restituisce false ... quindi nella linea di cattura: cattura ex quando filt (ex)
FastAl

1
@FastAl Non è ciò che i filtri di eccezione consentono in C # 6?
HimBromBeere,

@HimBromBeere sì, sono analoghi diretti
FastAl,

9

Aggiornamento 2015-12-15: consultare 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 da catturare una volta e filtrare le eccezioni, utilizzo un metodo di estensione come dimostrato di seguito.

Avevo già questa estensione nella mia libreria, originariamente scritta per altri scopi, ma ha funzionato perfettamente per typeverificare le eccezioni. Inoltre, imho, sembra più pulito di un mucchio di ||affermazioni. Inoltre, a differenza della risposta accettata, preferisco la gestione esplicita delle eccezioni, così ex is ...come il comportamento indesiderabile in quanto le classi derrived sono assegnabili ai tipi parent).

uso

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

Estensione IsAnyOf.cs (vedere l'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 della corrispondenza per i Exceptiontipi è esatto (es. Un bambino NON È una corrispondenza per nessuno dei suoi tipi principali).

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

1
Il miglioramento della lingua non è "più elegante". In molti posti questo ha effettivamente creato un inferno di manutenzione. Anni dopo, molti programmatori non sono orgogliosi del mostro che hanno creato. Non è quello che sei abituato a leggere. Potrebbe causare un "eh?" effetto, o anche "WTF" gravi. A volte è confuso. L'unica cosa che fa è rendere il codice molto più difficile da comprendere per coloro che devono occuparsene in seguito in manutenzione - solo perché un singolo programmatore ha cercato di essere "intelligente". Nel corso degli anni, ho imparato che quelle soluzioni "intelligenti" raramente sono anche quelle valide.
Kaii,

1
o in poche parole: attenersi alle possibilità che la lingua offre in modo nativo. non tenta di sovrascrivere la semantica di un linguaggio, solo perché tu non fai come loro. I tuoi colleghi (e forse anche il futuro) ti ringrazieranno, onestamente.
Kaii,

Inoltre, la tua soluzione si avvicina solo alla semantica di C # 6 when, come qualsiasi versione di catch (Exception ex) {if (...) {/*handle*/} throw;}. Il vero valore di whenè che il filtro viene eseguito prima che venga rilevata l'eccezione , evitando così il danneggiamento di spese / stack di un rilancio. Sfrutta una funzionalità CLR che in precedenza era accessibile solo a VB e MSIL.
Marc L.,

Più elegante? Questo esempio è così grande per un problema così semplice e il codice è così orribile che non valeva nemmeno la pena dare un'occhiata. Si prega di non rendere questo codice il problema di qualcun altro su un progetto reale.
KthProg,

l'intero IsAnyOfmetodo può essere riscritto semplicementep_comparisons.Contains(p_parameter)
maksymiuk,

7

Dato che mi sembrava 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 viene compilato, ad esempio:

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

Il motivo per cui lo vogliamo è perché non vogliamo che il gestore delle eccezioni catturi 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 osserviamo 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 ciò è notare che il tipo di base comune di entrambe le eccezioni è 'Eccezione', usalo per una variabile che contenga entrambe le eccezioni, quindi gestisci solo le due eccezioni che vengono rilevate. Il modo in cui questo è 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 ciò 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 del ramo, 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 utilizzando 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 prendere in considerazione la possibilità di catturare le giuste eccezioni e conservarle in modo 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.

7

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

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

Posso rilevare più eccezioni contemporaneamente?

In breve, no.

Il che porta alla domanda successiva,

Come 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. Impostare WebId sulla variabile temporanea completamente costruita. Imposta come dichiarazione finale del blocco try {}.

Quindi il codice è simile al seguente:

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 la costruzione del valore di fallback è costosa e il ripristino di un valore è molto più economico, spostare il codice di ripristino nella sua funzione:

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

Questa è una "codifica ecologica" piacevole, vale a dire che stai pensando al tuo codice e al tuo footprint di dati e assicurandoti che non vi siano perdite di valori elaborati a metà. Bello seguire questo schema grazie Jeffrey!
Tahir Khalid

6

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 delle eccezioni come già menzionato da altri. Quindi puoi modificare il codice sopra a questo:

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

3
"Mi chiedo perché nessuno abbia notato quella duplicazione del codice." - Cosa? L' intero punto della domanda è eliminare la duplicazione del codice.
Mark Amery,

4

Volevo aggiungere la mia breve risposta a questo thread già lungo. 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 usi un'eccezione "catch-all" come Eccezione , precederà tutte le altre dichiarazioni di cattura e otterrai ovviamente errori del compilatore, tuttavia se inverti l'ordine puoi incatenare le tue dichiarazioni di cattura (un po 'di un anti-pattern, penso ) puoi mettere il tipo di eccezione catch-all in fondo e questo catturerà tutte le eccezioni che non soddisfano più in alto nel tuo 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


4

Forse provi a mantenere il tuo codice semplice come mettere il codice comune in un metodo, come faresti in qualsiasi altra parte del codice che non è 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 semplice è bello


3

Nota che ho trovato un modo per farlo, ma 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;
    }
}

9
-1 voto, +5 WTF :-) Questo non avrebbe dovuto essere contrassegnato come una risposta, ma è lui-lario.
Aaron,

1
Non importa quanto semplicemente potremmo farlo. Ma non si è seduto inattivo e ha avuto la sua visione per risolverlo. Davvero apprezzato.
Maximo

2
In realtà non farlo però, usa i filtri di eccezione in C # 6 o una qualsiasi delle altre risposte - lo inserisco qui specificamente come "Questo è un modo, ma è male e voglio fare qualcosa di meglio".
Michael Stum

PERCHÉ è così male? Ero perplesso che non si potesse usare direttamente l'eccezione in un'istruzione switch.
MKesper,

3
@MKesper Vedo alcuni motivi per cui è male. Richiede la scrittura dei nomi di classe completi come valori letterali stringa, che è vulnerabile ai refusi da cui il compilatore non può salvarti. (Ciò è significativo poiché in molti negozi i casi di errore sono meno ben testati e quindi è più probabile che si verifichino errori banali in essi.) Non riuscirà nemmeno a far corrispondere un'eccezione che è una sottoclasse di uno dei casi specificati. Inoltre, a causa delle stringhe, i casi verranno persi da strumenti come "Trova tutti i riferimenti" di VS, pertinenti se si desidera aggiungere una fase di pulizia ovunque venga rilevata una particolare eccezione.
Mark Amery,

2

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 lanciare un 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
} 

0

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 overload del metodo "Catch", 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 l'implementazione di 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;
        }
    }

ps Non ho messo i controlli null per la semplicità del codice, considera di aggiungere convalide dei parametri.

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


-15

Basta chiamare il tentativo e prendere due volte.

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

È così semplice !!


3
um. questo sta sconfiggendo lo scopo della domanda. Fa questa domanda per sbarazzarsi del codice duplicato. questa risposta aggiunge altro codice duplicato.
James Esh,

-23

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

13
Questo esempio non mostra alcun uso dei filtri di eccezione.
user247702,

Questo è il modo standard per filtrare l'eccezione in c # 6.0
Kashif,

5
Dai un'occhiata a cosa sono esattamente i filtri di eccezione. Non stai usando un filtro di eccezione nel tuo esempio. C'è un esempio corretto in questa risposta postata un anno prima della tua.
user247702,

6
Un esempio di filtraggio eccezione sarebbecatch (HttpException e) when e.GetHttpCode() == 400 { WriteLine("Bad Request"; }
saluce
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.