La bolla Try / Finalmente (senza la cattura) sarà l'eccezione?


115

Sono quasi certo che la risposta sia SI. Se utilizzo un blocco Prova finalmente ma non utilizzo un blocco Catch, tutte le eccezioni VERRANNO gonfiate. Corretta?

Qualche idea sulla pratica in generale?

Seth

Risposte:


130

Sì, lo farà assolutamente. Supponendo che il tuo finallyblocco non generi un'eccezione, ovviamente, nel qual caso ciò "sostituirà" effettivamente quello che è stato originariamente lanciato.


13
@ David: non puoi tornare da un blocco finale in C #.
Jon Skeet

3
La documentazione di msdn conferma anche questa risposta: in alternativa, puoi catturare l'eccezione che potrebbe essere generata nel blocco try di un'istruzione try-latest più in alto nello stack di chiamate. Cioè, puoi catturare l'eccezione nel metodo che chiama il metodo che contiene l'istruzione try-latest, o nel metodo che chiama quel metodo o in qualsiasi metodo nello stack di chiamate. Se l'eccezione non viene rilevata, l'esecuzione del blocco latest dipende dal fatto che il sistema operativo scelga di attivare un'operazione di annullamento dell'eccezione.
banda larga

60

Qualche idea sulla pratica in generale?

Sì. Stai attento . Quando il tuo blocco finalmente è in esecuzione, è del tutto possibile che sia in esecuzione perché è stata lanciata un'eccezione imprevista non gestita . Ciò significa che qualcosa è rotto e potrebbe accadere qualcosa di completamente inaspettato .

In quella situazione è probabilmente il caso che non dovresti affatto eseguire codice in blocchi alla fine. Il codice nel blocco finalmente potrebbe essere costruito per presumere che i sottosistemi da cui dipende siano integri, quando in realtà potrebbero essere profondamente danneggiati. Il codice nel blocco finalmente potrebbe peggiorare le cose.

Ad esempio, vedo spesso questo genere di cose:

DisableAccessToTheResource();
try
{
    DoSomethingToTheResource();
}
finally
{
    EnableAccessToTheResource();
}

L'autore di questo codice sta pensando "Sto apportando una mutazione temporanea allo stato del mondo; ho bisogno di riportare lo stato a quello che era prima di essere chiamato". Ma pensiamo a tutti i modi in cui ciò potrebbe andare storto.

Innanzitutto, l'accesso alla risorsa potrebbe già essere disabilitato dal chiamante; in tal caso, questo codice lo riattiva, possibilmente prematuramente.

Secondo, se DoSomethingToTheResource genera un'eccezione, è la cosa giusta da fare per abilitare l'accesso alla risorsa ??? Il codice che gestisce la risorsa è inaspettatamente interrotto . Questo codice dice, in effetti, "se il codice di gestione è danneggiato, assicurati che un altro codice possa chiamare quel codice non funzionante il prima possibile, in modo che possa anche fallire in modo orribile ". Sembra una cattiva idea.

Terzo, se DoSomethingToTheResource genera un'eccezione, come facciamo a sapere che anche EnableAccessToTheResource non genererà un'eccezione? Qualunque sia la gravità dell'uso della risorsa potrebbe anche influenzare il codice di pulizia, nel qual caso l'eccezione originale andrà persa e il problema sarà più difficile da diagnosticare.

Tendo a scrivere codice come questo senza utilizzare i blocchi di prova finale:

bool wasDisabled = IsAccessDisabled();
if (!wasDisabled)
    DisableAccessToTheResource();
DoSomethingToTheResource();
if (!wasDisabled)
    EnableAccessToTheResource();

Ora lo stato non è mutato a meno che non sia necessario. Ora lo stato del chiamante non è incasinato. E ora, se DoSomethingToTheResource fallisce, non riabilitiamo l'accesso. Partiamo dal presupposto che qualcosa sia profondamente rotto e non rischiamo di peggiorare la situazione cercando di mantenere il codice in esecuzione. Lascia che il chiamante si occupi del problema, se può.

Allora, quando è una buona idea eseguire un blocco finale? Primo, quando è prevista l'eccezione. Ad esempio, potresti aspettarti che un tentativo di bloccare un file possa fallire, perché qualcun altro lo ha bloccato. In tal caso ha senso rilevare l'eccezione e segnalarla all'utente. In quel caso si riduce l'incertezza su ciò che è rotto; è improbabile che peggiorerai le cose pulendo.

In secondo luogo, quando la risorsa che stai ripulendo è una risorsa di sistema scarsa. Ad esempio, ha senso chiudere un handle di file in un blocco finalmente. (Un "utilizzo" è ovviamente solo un altro modo di scrivere un blocco di prova finale.) Il contenuto del file potrebbe essere danneggiato, ma non c'è niente che tu possa fare al riguardo ora. L'handle del file verrà chiuso alla fine, quindi potrebbe anche essere prima o poi.


7
Ancora altri esempi di come non abbiamo ancora messo insieme il nostro agire come settore quando si tratta di gestione degli errori. Non che io abbia qualcosa di meglio da suggerire delle eccezioni, ma spero che il futuro riservi qualcosa di più probabile che porti ad intraprendere la giusta linea di condotta ragionevolmente facilmente.
Jon Skeet,

In vb.net, è possibile che il codice in un blocco Infine sappia quale eccezione è in sospeso. Se si imposta una gerarchia di eccezioni per distinguere "non l'ho fatto; stato altrimenti okay" da "lo stato è rotto", si potrebbe eseguire un blocco Finalmente solo se l'eccezione in sospeso non è una di quelle cattive. Mi piace che le routine disposer / cleanup catturino le eccezioni e generino un'eccezione DisposerFailedException (che è nella categoria "cattiva" e include l'eccezione originale come InnerException). Mi piacerebbe vedere un'interfaccia iDisposableEx standard con un Dispose (Ex come eccezione) per facilitare questo.
supercat

Sebbene apprezzi il fatto che ci siano casi in cui può verificarsi un'eccezione mentre un oggetto è in uno stato di cattiva qualità e il tentativo di pulizia peggiorerà le cose, penso che tali problemi dovrebbero essere gestiti nel codice di pulizia, possibilmente con l'aiuto dei delegati. Un wrapper di blocco, ad esempio, potrebbe esporre un delegato della funzione "CheckIfSafeToUnlock (Ex come eccezione)" che potrebbe essere impostato nei momenti in cui l'oggetto bloccato è in uno stato non valido e cancellato altrimenti. Prima di rilasciare il blocco, il wrapper potrebbe controllare il delegato; se non è vuoto, potrebbe eseguirlo e rilasciare il blocco solo se restituisce true.
supercat

Avere un wrapper che utilizza un delegato di questo tipo consentirebbe al codice che ha manipolato lo stato dell'oggetto di selezionare l'azione di pulizia appropriata se è interrotto. Tali azioni potrebbero includere la riparazione della struttura dei dati e la restituzione di True, il lancio di un'altra eccezione "più grave" che avvolge quella originale, o il filtraggio dell'eccezione originale durante la restituzione di False.
supercat

2
Eric, grazie per la tua ottima risposta. Immagino che avrei dovuto fare due domande perché sia ​​tu che Jon avete ragione. In ogni caso, sei votato. Grazie per l'attenzione alla domanda.
Seth Spearman
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.