Perché provare {...} finalmente {...} è buono; provare {...} catturare {} male?


201

Ho visto la gente dire che è una cattiva forma usare catch senza argomenti, specialmente se quel catch non fa nulla:

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
catch   // No args, so it will catch any exception
{}
reader.Close();

Tuttavia, questa è considerata una buona forma:

StreamReader reader=new  StreamReader("myfile.txt");
try
{
  int i = 5 / 0;
}
finally   // Will execute despite any exception
{
  reader.Close();
}

Per quanto ne so, l'unica differenza tra l'inserimento del codice di pulizia in un blocco di fine e l'inserimento del codice di pulizia dopo i blocchi di prova..catch è se nel blocco di prova sono presenti dichiarazioni di ritorno (in tal caso, il codice di pulizia in eseguito, ma il codice dopo il try..catch no).

Altrimenti, cosa c'è di così speciale alla fine?


7
Prima di provare a catturare una tigre che non puoi gestire, dovresti documentare i tuoi desideri.

L' argomento delle eccezioni nella documentazione può fornire alcune buone intuizioni. Dai anche un'occhiata all'esempio di Finalmente Block .
Athafoud,

Risposte:


357

La grande differenza è che try...catchinghiottirà l'eccezione, nascondendo il fatto che si è verificato un errore. try..finallyeseguirà il codice di pulizia e quindi l'eccezione continuerà, per essere gestita da qualcosa che sa cosa farne.


11
È probabile che qualsiasi codice scritto pensando all'incapsulamento sia in grado di gestire l'eccezione solo nel punto in cui viene generato. Semplicemente passarlo indietro nello stack di chiamate nella disperata speranza che qualcos'altro sarà in grado di gestire alcune eccezioni arbitrarie è una ricetta per il disastro.
David Arno,

3
Nella maggior parte dei casi, è più evidente il motivo per cui si verificherebbe una particolare eccezione a livello di applicazione (ad esempio, una determinata impostazione di configurazione) rispetto al livello di libreria della classe.
Mark Cidade,

88
David - Preferirei che il programma fallisse rapidamente in modo da poter essere consapevole del problema piuttosto che lasciare il programma in esecuzione in uno stato sconosciuto.
Erik Forbes,

6
Se il tuo programma si trova in uno stato sconosciuto dopo un'eccezione, stai sbagliando il codice.
Zan Lynx,

41
@DavidArno, qualsiasi codice scritto pensando all'incapsulamento dovrebbe gestire solo le eccezioni nel loro ambito. Qualsiasi altra cosa dovrebbe essere passata per qualcun altro da gestire. Se ho un'applicazione che ottiene un nome di file dagli utenti, quindi legge il file e il mio lettore di file riceve un'eccezione aprendo il file, dovrebbe passarli (o consumare l'eccezione e lanciarne uno nuovo) in modo che l'applicazione possa dire , ehi - il file non si è aperto, chiediamo all'utente un altro. Il lettore di file non dovrebbe essere in grado di richiedere agli utenti o intraprendere altre azioni in risposta. Il suo unico scopo è leggere i file.
iheanyi,

62

"Finalmente" è una frase di "Qualcosa che devi sempre fare per assicurarti che lo stato del programma sia sano". Come tale, è sempre una buona forma averne uno, se esiste la possibilità che le eccezioni possano annullare lo stato del programma. Il compilatore inoltre fa di tutto per assicurarsi che il tuo codice Infine sia eseguito.

"Cattura" è una dichiarazione di "Posso recuperare da questa eccezione". Dovresti solo riprenderti dalle eccezioni che puoi davvero correggere: la cattura senza argomenti dice "Ehi, posso recuperare da qualsiasi cosa!", Il che è quasi sempre falso.

Se fosse possibile recuperare da ogni eccezione, sarebbe davvero un cavillo semantico, su ciò che stai dichiarando di essere. Tuttavia, non lo è, e quasi sicuramente i frame sopra i tuoi saranno meglio equipaggiati per gestire alcune eccezioni. Come tale, utilizzare infine, ottenere il codice di pulizia eseguito gratuitamente, ma lasciare comunque che gestori più esperti affrontino il problema.


1
Il tuo sentimento è diffuso, ma sfortunatamente ignora un altro caso importante: invalidare espressamente un oggetto i cui invarianti potrebbero non essere più validi. Un modello comune è che il codice acquisisca un blocco, apporti alcune modifiche a un oggetto e rilasci il blocco. Se si verifica un'eccezione dopo aver apportato alcune ma non tutte le modifiche, l'oggetto potrebbe essere lasciato in uno stato non valido. Anche se dovrebbero esistere alternative migliori IMHO , non conosco un approccio migliore che catturare qualsiasi eccezione che si verifichi mentre lo stato dell'oggetto può essere non valido, invalidare espressamente lo stato e riprovare.
supercat,

32

Perché quando quella singola riga genera un'eccezione, non lo sapresti.

Con il primo blocco di codice, l'eccezione verrà semplicemente assorbita , il programma continuerà a essere eseguito anche quando lo stato del programma potrebbe essere errato.

Con il secondo blocco, l'eccezione verrà generata e sgorga ma la reader.Close()continua sempre eseguita.

Se non si prevede un'eccezione, non mettere un blocco try..catch proprio così, sarà difficile eseguire il debug in seguito quando il programma è andato in cattivo stato e non hai idea del perché.


21

Alla fine viene eseguito qualunque cosa. Quindi, se il blocco try ha avuto esito positivo, verrà eseguito, se il blocco try non riesce, eseguirà il blocco catch e quindi il blocco finally.

Inoltre, è meglio provare a usare il seguente costrutto:

using (StreamReader reader=new  StreamReader("myfile.txt"))
{
}

Poiché l'istruzione using viene automaticamente racchiusa in un try / finally e lo stream verrà automaticamente chiuso. (Dovrai mettere un try / catch intorno all'istruzione using se vuoi effettivamente catturare l'eccezione).


5
Questo non è corretto L'uso non avvolge il codice con try / catch, dovrebbe dire try / finally
pr0nin

8

Sebbene i seguenti 2 blocchi di codice siano equivalenti, non sono uguali.

try
{
  int i = 1/0; 
}
catch
{
  reader.Close();
  throw;
}

try
{
  int i = 1/0;
}
finally
{
  reader.Close();
}
  1. "finalmente" è un codice che rivela l'intenzione. Dichiarate al compilatore e ad altri programmatori che questo codice deve essere eseguito, qualunque cosa accada.
  2. se hai più blocchi di cattura e hai un codice di pulizia, devi finalmente. Senza infine, duplicheresti il ​​codice di pulizia in ogni blocco catch. (Principio SECCO)

finalmente i blocchi sono speciali. Il CLR riconosce e tratta il codice inserendo un blocco finally separatamente dai blocchi catch e il CLR fa di tutto per garantire che un blocco finally verrà sempre eseguito. Non è solo zucchero sintattico dal compilatore.


5

Sono d'accordo con quello che sembra essere il consenso qui: un "fermo" vuoto è negativo perché maschera qualsiasi eccezione si sia verificata nel blocco try.

Inoltre, dal punto di vista della leggibilità, quando vedo un blocco "try" presumo che ci sarà una corrispondente dichiarazione "catch". Se stai solo usando un 'prova' per assicurarti che le risorse siano allocate nel blocco 'finally', potresti considerare invece l' istruzione 'using' :

using (StreamReader reader = new StreamReader('myfile.txt'))
{
    // do stuff here
} // reader.dispose() is called automatically

È possibile utilizzare l'istruzione "using" con qualsiasi oggetto che implementa IDisposable. Il metodo dispose () dell'oggetto viene chiamato automaticamente alla fine del blocco.


4

Uso Try..Catch..Finally , se il metodo in uso sa come gestire l'eccezione localmente. L'eccezione si verifica in Try, Handled in Catch e dopo che la pulizia è stata eseguita in Final.

Nel caso in cui il metodo non sappia come gestire l'eccezione ma necessita di una pulizia una volta che si è verificato, utilizzare Try..Finally

In questo modo l'eccezione viene propagata ai metodi di chiamata e gestita se ci sono istruzioni Catch adatte nei metodi di chiamata. Se non ci sono gestori di eccezioni nel metodo corrente o in uno dei metodi di chiamata, l'applicazione si arresta in modo anomalo.

Con Try..Finallyè garantito che l'alto pulito locale viene fatto prima moltiplicazione l'eccezione ai metodi di chiamata.


1
Per quanto fondamentale sia questa risposta, è assolutamente la migliore. È bello avere l'abitudine di provare / catturare / finalmente, anche se uno di questi ultimi due viene lasciato vuoto. Ci sono circostanze MOLTO rare in cui può esistere un blocco catch ed essere vuoto, ma almeno se scrivi sempre try / catch / infine, vedrai il blocco vuoto mentre stai esaminando il codice. Avere un blocco finalmente vuoto è utile allo stesso modo. Se è necessario eseguire la pulizia in un secondo momento o se è necessario eseguire il debug di uno stato al momento dell'eccezione, è incredibilmente utile.
Jesse Williams,

3

Il blocco try..finally genererà comunque eventuali eccezioni che vengono sollevate. Tutto finallyciò che serve è assicurarsi che il codice di pulizia venga eseguito prima che venga generata l'eccezione.

Il tentativo..catch con un catch vuoto consumerà completamente qualsiasi eccezione e nasconderà il fatto che è successo. Il lettore sarà chiuso, ma non si può dire se sia avvenuta la cosa giusta. E se il tuo intento fosse quello di scrivere i nel file? In questo caso, non arriverai a quella parte del codice e myfile.txt sarà vuoto. Tutti i metodi a valle lo gestiscono correttamente? Quando vedi il file vuoto, sarai in grado di indovinare correttamente che è vuoto perché è stata generata un'eccezione? Meglio gettare l'eccezione e far sapere che stai facendo qualcosa di sbagliato.

Un altro motivo è il tentativo..catch fatto in questo modo è completamente errato. Ciò che stai dicendo è "Non importa cosa succede, posso gestirlo". Che direStackOverflowException , puoi pulire dopo? Che dire OutOfMemoryException? In generale, dovresti gestire solo le eccezioni che ti aspetti e sapere come gestirle.


2

Se non sai quale tipo di eccezione catturare o cosa fare con esso, non ha senso avere una dichiarazione catch. Dovresti semplicemente lasciarlo per un chiamante più in alto che potrebbe avere maggiori informazioni sulla situazione per sapere cosa fare.

Dovresti comunque avere un'istruzione finally in caso ci sia un'eccezione, in modo da poter ripulire le risorse prima che quell'eccezione venga lanciata al chiamante.


2

Dal punto di vista della leggibilità, dice in modo più esplicito ai futuri lettori di codici "questa roba qui è importante, deve essere fatta qualunque cosa accada". Questo è buono.

Inoltre, le dichiarazioni di cattura vuote tendono ad avere un certo "odore" per loro. Potrebbero essere un segno che gli sviluppatori non stanno riflettendo sulle varie eccezioni che possono verificarsi e su come gestirle.


2

Finalmente è facoltativo - non c'è motivo di avere un blocco "Finalmente" se non ci sono risorse da ripulire.


2

Tratto da: qui

Generare e catturare eccezioni non dovrebbe avvenire di routine come parte dell'esecuzione corretta di un metodo. Quando si sviluppano librerie di classi, è necessario offrire al codice client l'opportunità di verificare una condizione di errore prima di intraprendere un'operazione che può comportare la generazione di un'eccezione. Ad esempio, System.IO.FileStream fornisce una proprietà CanRead che può essere verificata prima di chiamare il metodo Read, evitando che venga sollevata una potenziale eccezione, come illustrato nel seguente frammento di codice:

Dim str As Stream = GetStream () If (str.CanRead) Quindi 'codice per leggere lo stream End If

La decisione di verificare lo stato di un oggetto prima di invocare un metodo particolare che può sollevare un'eccezione dipende dallo stato previsto dell'oggetto. Se un oggetto FileStream viene creato utilizzando un percorso file che dovrebbe esistere e un costruttore che dovrebbe restituire un file in modalità lettura, non è necessario controllare la proprietà CanRead; l'incapacità di leggere il FileStream sarebbe una violazione del comportamento previsto delle chiamate al metodo effettuate e dovrebbe essere sollevata un'eccezione. Al contrario, se è documentato un metodo che restituisce un riferimento FileStream che può essere o meno leggibile, è consigliabile controllare la proprietà CanRead prima di tentare di leggere i dati.

Per illustrare l'impatto delle prestazioni che può causare l'uso di una tecnica di codifica "esegui fino a eccezione", le prestazioni di un cast, che genera un InvalidCastException se il cast fallisce, vengono confrontate con C # come operatore, che restituisce valori nulli se un cast fallisce. Le prestazioni delle due tecniche sono identiche per il caso in cui il cast è valido (vedere Test 8.05), ma per il caso in cui il cast non è valido e l'utilizzo di un cast provoca un'eccezione, l'utilizzo di un cast è 600 volte più lento rispetto all'utilizzo del come operatore (vedi Test 8.06). L'impatto ad alte prestazioni della tecnica di lancio delle eccezioni include il costo di allocazione, lancio e cattura dell'eccezione e il costo della successiva raccolta dei rifiuti dell'oggetto eccezione, il che significa che l'impatto istantaneo del lancio di un'eccezione non è così elevato. Man mano che vengono generate più eccezioni,


2
Scott - se il testo che hai citato sopra è dietro il paywall di expertexchange.com, probabilmente non dovresti pubblicarlo qui. Potrei sbagliarmi su questo, ma scommetto che non è una buona idea.
Onorio Catenacci,

2

È una cattiva pratica aggiungere una clausola catch solo per riproporre l'eccezione.


2

Se leggerai C # per i programmatori , capirai che il blocco finally è stato progettato per ottimizzare un'applicazione e prevenire la perdita di memoria.

Il CLR non elimina completamente le perdite ... Possono verificarsi perdite di memoria se il programma mantiene inavvertitamente riferimenti a oggetti indesiderati

Ad esempio, quando si apre una connessione al file o al database, la macchina allocherà memoria per soddisfare quella transazione e tale memoria non verrà conservata a meno che non sia stato eseguito il comando disposto o chiuso. ma se durante la transazione si è verificato un errore, il comando procedente verrà chiuso non a meno che non si trovasse all'interno del try.. finally..blocco.

catchera diverso dal finallysenso che, catturare era design per darti modo di gestire / gestire o interpretare l'errore da solo. Pensala come una persona che ti dice "hey ho catturato dei cattivi, cosa vuoi che faccia loro?" mentrefinally stato progettato per assicurarsi che le risorse siano state posizionate correttamente. Pensa a qualcuno che, indipendentemente dal fatto che ci siano o meno dei cattivi, si assicurerà che la tua proprietà sia ancora al sicuro.

E dovresti permettere a quei due di lavorare insieme per sempre.

per esempio:

try
{
  StreamReader reader=new  StreamReader("myfile.txt");
  //do other stuff
}
catch(Exception ex){
 // Create log, or show notification
 generic.Createlog("Error", ex.message);
}
finally   // Will execute despite any exception
{
  reader.Close();
}

1

Con, infine, puoi ripulire le risorse, anche se l'istruzione catch lancia l'eccezione al programma chiamante. Con il tuo esempio contenente l'istruzione catch vuota, c'è poca differenza. Tuttavia, se nella tua cattura, esegui un po 'di elaborazione e lanci l'errore, o addirittura non hai nemmeno una presa, finalmente verrà comunque eseguito.


1

Bene per uno, è una cattiva pratica catturare eccezioni che non ti preoccupi di gestire. Consulta il capitolo 5 sulle prestazioni .Net dal miglioramento delle prestazioni e della scalabilità delle applicazioni .NET . Nota a margine, probabilmente dovresti caricare il flusso all'interno del blocco try, in questo modo, puoi catturare l'eccezione pertinente se fallisce. La creazione dello stream all'esterno del blocco try ne vanifica lo scopo.


0

Tra probabilmente molte ragioni, le eccezioni sono molto lente da eseguire. Puoi facilmente paralizzare i tuoi tempi di esecuzione se ciò accade molto.


0

Il problema con i blocchi try / catch che rilevano tutte le eccezioni è che il programma è ora in uno stato indeterminato se si verifica un'eccezione sconosciuta. Questo va completamente contro la regola del fail fast - non vuoi che il tuo programma continui se si verifica un'eccezione. Il precedente try / catch catturerebbe anche OutOfMemoryExceptions, ma questo è sicuramente uno stato in cui il tuo programma non verrà eseguito.

I blocchi di prova / infine consentono di eseguire il codice di pulizia pur continuando a fallire rapidamente. Nella maggior parte dei casi, si desidera solo catturare tutte le eccezioni a livello globale, in modo da poterle registrare e quindi uscire.


0

La differenza effettiva tra i tuoi esempi è trascurabile purché non vengano generate eccezioni.

Se, tuttavia, viene generata un'eccezione nella clausola 'try', il primo esempio la inghiottirà completamente. Il secondo esempio solleverà l'eccezione al passaggio successivo dello stack di chiamate, quindi la differenza negli esempi dichiarati è che uno oscura completamente qualsiasi eccezione (primo esempio) e l'altro (secondo esempio) conserva le informazioni sull'eccezione per una potenziale gestione successiva mentre sta ancora eseguendo il contenuto nella clausola 'finally'.

Se, ad esempio, dovessi inserire il codice nella clausola "catch" del primo esempio che ha generato un'eccezione (o quella inizialmente sollevata o nuova), il codice di pulizia del lettore non verrebbe mai eseguito. Viene infine eseguito indipendentemente da ciò che accade nella clausola "catch".

Quindi, la principale differenza tra 'catch' e 'finally' è che i contenuti del blocco 'finally' (con alcune rare eccezioni) possono essere considerati garantiti per l'esecuzione, anche di fronte a un'eccezione imprevista, mentre qualsiasi codice che segue una clausola di "cattura" (ma al di fuori di una clausola "finalmente") non avrebbe tale garanzia.

Per inciso, Stream e StreamReader implementano entrambi IDisposable e possono essere racchiusi in un blocco "using". I blocchi 'using' sono l'equivalente semantico di try / finally (no 'catch'), quindi il tuo esempio potrebbe essere espresso più tersamente come:

using (StreamReader reader = new  StreamReader("myfile.txt"))
{
  int i = 5 / 0;
}

... che chiuderà e eliminerà l'istanza StreamReader quando non rientra nell'ambito. Spero che questo ti aiuti.


0

provare {...} catturare {} non è sempre male. Non è un modello comune, ma tendo a usarlo quando ho bisogno di arrestare le risorse a prescindere da cosa, come chiudere un socket (forse) aperto alla fine di un thread.

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.