Novembre 2015

Volume 30 Numero 12

Il presente articolo è stato tradotto automaticamente.

Essential .NET - Gestione delle eccezioni C#

Da Mark Michaelis | Novembre 2015

Mark MichaelisBenvenuto nel primo articolo di Essential .NET. È qui dove sarà possibile seguire tutto ciò che avviene nel mondo Microsoft .NET Framework, la presenza di avanza in c# vNext (attualmente c# 7.0), migliorata internals .NET o sugli eventi sul pannello anteriore Roslyn e Core .NET (ad esempio, MSBuild spostamento aprire origine).

Stato ho la scrittura e lo sviluppo con .NET dal momento che è stato annunciato in anteprima nel 2000. Molte delle quali scriverò su sarà solo nuove funzioni, ma su come utilizzare la tecnologia tenendo in considerazione le procedure consigliate.

Vivo a Spokane, Washington, dove sono "Chief Nerd" di una società di consulenza high-end denominata IntelliTect (IntelliTect.com). IntelliTect è specializzato in sviluppo "stuff disco", con excellence. Sono stato un MVP Microsoft (attualmente per c#) succedendo 20 anni e un direttore regionale Microsoft per otto di quegli anni. Oggi, questa colonna viene avviata con uno sguardo alle linee guida di gestione delle eccezioni aggiornato.

C# 6.0 incluse due nuove funzionalità di gestione delle eccezioni. In primo luogo, incluso il supporto per le condizioni di eccezione, ovvero la possibilità di fornire un'espressione che filtra un'eccezione dall'inserimento di blocco catch prima il fluire dello stack. In secondo luogo, è incluso il supporto asincrono all'interno di un blocco catch, qualcosa che non era possibile in c# 5.0 quando asincrono è stato aggiunto al linguaggio. Inoltre, sono state molte altre modifiche che si sono verificati nelle ultime cinque versioni di c# e .NET Framework corrispondente, le modifiche, che in alcuni casi, sono sufficientemente elevato da richiedere modifiche alle linee guida di codifica c#. In questo articolo verrà esaminare un numero di queste modifiche e fornire indicazioni aggiornate correlate alla gestione delle eccezioni, intercettazione di eccezioni.

Intercettazione di eccezioni: Recensione

È abbastanza bene riconosciuto, la generazione di un particolare tipo di eccezione consente la ricezione da utilizzare il tipo di eccezione stesso per identificare il problema. Non è necessario, in altre parole, per rilevare l'eccezione e utilizzare un'istruzione switch nel messaggio dell'eccezione per determinare l'azione da intraprendere alla luce dell'eccezione. Invece di c# per più blocchi catch, ciascuno destinato a un tipo di eccezione specifico come illustrato nella Figura 1.

Figura 1 intercettazione di tipi diversi di eccezione

using System;
public sealed class Program
{
  public static void Main(string[] args)
    try
    {
       // ...
      throw new InvalidOperationException(
         "Arbitrary exception");
       // ...
   }
   catch(System.Web.HttpException exception)
     when(exception.GetHttpCode() == 400)
   {
     // Handle System.Web.HttpException where
     // exception.GetHttpCode() is 400.
   }
   catch (InvalidOperationException exception)
   {
     bool exceptionHandled=false;
     // Handle InvalidOperationException
     // ...
     if(!exceptionHandled)
       // In C# 6.0, replace this with an exception condition
     {
        throw;
     }
    }  
   finally
   {
     // Handle any cleanup code here as it runs
     // regardless of whether there is an exception
   }
 }
}

Quando si verifica un'eccezione, l'esecuzione passerà al primo blocco catch che può essere gestito. Se è presente più di un blocco catch associato il blocco try, il livello di una corrispondenza è determinata dalla catena di ereditarietà (presupponendo che la condizione di eccezione non c# 6.0) e il primo in modo che corrispondano elaborerà l'eccezione. Ad esempio, anche se l'eccezione generata è di tipo System. Exception, questo "è un" relazione si verifica tramite ereditarietà perché System. InvalidOperationException derivano da System. Exception. Poiché InvalidOperationException corrisponde maggiormente l'eccezione generata, catch(InvalidOperationException...) rileverà l'eccezione e non catch(Exception...) blocco se si è verificato uno.

Rilevare i blocchi devono apparire in ordine (non presupponendo nuovamente alcuna condizione di eccezione c# 6.0), dal più specifico a più generale, per evitare un errore in fase di compilazione. Ad esempio, aggiunta di un blocco catch(Exception...) prima delle altre eccezioni comporterà un errore di compilazione poiché tutte le eccezioni precedenti derivano da System. Exception in un punto della propria catena di ereditarietà. Si noti inoltre che un parametro denominato per il blocco catch non è necessario. In realtà, un blocco catch finale senza il tipo di parametro è consentito, purtroppo, come descritto nel blocco catch generale.

In alcuni casi, dopo il rilevamento di un'eccezione, è possibile stabilire che, in realtà, non è possibile gestire in modo adeguato l'eccezione. In questo scenario, sono disponibili due opzioni principali. La prima opzione consiste nel generare nuovamente un'eccezione diversa. Esistono tre scenari comuni per questo quando potrebbe essere utile:

Scenario n. 1 eccezione acquisita non è sufficientemente identifica il problema che ha attivato. Ad esempio, quando si chiama System.Net.WebClient.DownloadString con un URL valido, il runtime potrebbe generare un System.Net.WebException quando non esiste alcuna connessione di rete, ovvero la stessa eccezione generata con un URL inesistente.

Scenario n. 2 eccezione acquisito include dati privati che non devono essere esposti superiore la catena di chiamate. Ad esempio, una versione preliminare di CLR v1 (pre-alpha, persino) verificata un'eccezione che ha un aspetto simile, "eccezione di sicurezza: Si dispone dell'autorizzazione per determinare il percorso del c:\temp\foo.txt."

Scenario n. 3 è troppo specifico per il chiamante di gestire il tipo di eccezione. Ad esempio System.IO (ad esempio UnauthorizedAccessException IOException FileNotFoundException DirectoryNotFoundException PathTooLongException, NotSupportedException o SecurityException ArgumentException) si verifica un'eccezione nel server durante la chiamata di un servizio Web per cercare un codice postale.

Durante la rigenerazione di un'eccezione diversa, prestare attenzione al fatto che potrebbe perdere l'eccezione originale (presumibilmente intenzionalmente per Scenario 2). Per evitare questo problema, impostare proprietà InnerException dell'eccezione di ritorno a capo, in genere assegnabile tramite il costruttore, con l'eccezione rilevata, a meno che in tal modo espone dati privati che non devono essere esposte superiore nella catena di chiamate. In questo modo, la traccia dello stack originale è ancora disponibile.

Se non si imposta l'eccezione interna e ancora specificare l'istanza di eccezione dopo l'istruzione throw (genera un'eccezione) la traccia dello stack di posizione verrà impostata sull'istanza di eccezione. Anche se si rigenera l'eccezione intercettata in precedenza, la cui traccia dello stack è già impostato, verrà reimpostato.

Una seconda opzione quando il rilevamento di un'eccezione consiste nel determinare che, in realtà, è possibile in modo appropriato gestirlo. In questo scenario sarà necessario rigenerare la stessa eccezione esatta, inviarlo al gestore la catena di chiamate successivo. Blocco di genera InvalidOperationException Figura 1 viene illustrata questa evenienza. Un'istruzione throw priva di qualsiasi identificazione dell'eccezione da generare (throw è autonomo), anche se viene visualizzata un'istanza di eccezione (eccezione) nell'ambito di blocco catch che può essere rigenerata. Generare un'eccezione specifica Aggiorna tutte le informazioni dello stack per corrispondere alla nuova posizione throw. Di conseguenza, tutte le informazioni sullo stack che indica il sito di chiamata dell'origine eccezione andrebbe perso, rendendo molto più difficile da diagnosticare il problema. Al momento di determinare che un blocco catch non è sufficientemente gestire un'eccezione, l'eccezione deve essere rigenerata tramite l'istruzione throw vuota.

Si sta rigenerare l'eccezione stessa o il wrapping di un'eccezione, la linea guida generale consiste nell'evitare eccezioni reporting o registrazione inferiore nello stack di chiamate. In altre parole, non registra un'eccezione ogni volta che si catch e come rigenerarla. In questo modo causa confusione nei file di log senza aggiungere valore perché la stessa cosa verrà registrata ogni volta. Inoltre, l'eccezione include i dati di traccia dello stack di quando è non stata generata, pertanto è necessario registrare che ogni ora. Registrare l'eccezione adeguati ogni volta che viene gestito o, nel caso che non sta per essere gestiti, per registrare l'eccezione prima di arrestare un processo di log.

Generazione di eccezioni esistente senza sostituire le informazioni dello Stack

In c# 5.0, è stato aggiunto un meccanismo che consente la generazione di un'eccezione generata in precedenza senza perdere le informazioni della traccia dello stack dell'eccezione originale. Consente di rigenerare le eccezioni, ad esempio, anche da all'esterno di un blocco catch e, pertanto, senza utilizzare un oggetto vuoto genera. Sebbene sia piuttosto raro, è necessario eseguire questa operazione, in alcuni casi le eccezioni sono incapsulate o salvate fino a quando l'esecuzione del programma viene spostato all'esterno del blocco catch. Codice multithreading, ad esempio, potrebbe disporre di un'eccezione con un oggetto AggregateException. .NET Framework 4.5 fornisce una classe System.Runtime.ExceptionServices.ExceptionDispatchInfo in modo specifico per gestire questo scenario mediante l'acquisizione statico e Throw metodi di istanza. Figura 2 illustra rigenerare l'eccezione senza reimpostare le informazioni sulla traccia dello stack o tramite un'istruzione throw vuota.

Figura 2 utilizzo ExceptionDispatchInfo per generare nuovamente un'eccezione

using System
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
Task task = WriteWebRequestSizeAsync(url);
try
{
  while (!task.Wait(100))
{
    Console.Write(".");
  }
}
catch(AggregateException exception)
{
  exception = exception.Flatten();
  ExceptionDispatchInfo.Capture(
    exception.InnerException).Throw();
}

Il metodo ExeptionDispatchInfo.Throw, il compilatore non viene considerato un'istruzione return nello stesso modo che è possibile che un'istruzione throw normale. Ad esempio, se la firma del metodo ha restituito un valore, ma il percorso del codice con ExceptionDispatchInfo.Throw non ha restituito alcun valore, il compilatore genera un errore che indica che è stato restituito alcun valore. In alcuni casi, gli sviluppatori che si debba eseguire ExceptionDispatchInfo.Throw con un'istruzione return, anche se tale istruzione non verrebbe eseguito in fase di esecuzione, verrà generata l'eccezione invece.

Intercettazione di eccezioni nel linguaggio c# 6.0

La linea guida di gestione delle eccezioni generali consiste nell'evitare di rilevare le eccezioni che è possibile risolvere completamente. Poiché le espressioni catch rispetto a c# 6.0 potrebbero filtrare solo dal tipo di eccezione, la possibilità di controllare i dati dell'eccezione e il contesto prima della rimozione dello stack nel blocco catch necessari catch blocco per diventare il gestore dell'eccezione prima di esaminare il. Sfortunatamente, dopo la determinazione di non gestire l'eccezione, è complicato da scrivere codice che consenta a un blocco catch diversi nello stesso contesto per gestire l'eccezione. Offrendo, rigenerare gli stessi risultati di eccezione di dover richiamare il processo in due passaggi eccezione nuovamente, un processo che prevede innanzitutto l'eccezione la catena di chiamate fino a quando non ne viene individuato uno che gestisce e, secondo, rimozione dello stack di chiamata per ogni fotogramma tra l'eccezione e il percorso di catch.

Una volta che viene generata un'eccezione, anziché lo stack di chiamate al blocco catch per l'eccezione rigenerata perché ha rivelato ulteriore esame dell'eccezione che Impossibile essere gestita sufficientemente la rimozione, che verrebbe ovviamente essere preferibile non intercettare l'eccezione in primo luogo. A partire da c# 6.0, un'espressione condizionale aggiuntiva è disponibile per i blocchi catch. Invece di limitazione se un blocco catch blocco corrispondenze basate solo su una corrispondenza di tipo di eccezione, in c# 6.0 include il supporto per una clausola condizionale. La clausola consente di fornire un'espressione booleana che ulteriori filtri quando si blocco catch all'handle solo l'eccezione se la condizione è true. System.Web.HttpException il blocco in Figura 1 questo illustrati con un operatore di confronto di uguaglianza.

Un risultato interessante della condizione di eccezione è che, quando una condizione di eccezione viene fornito, il compilatore non imporre blocchi catch appaiano nell'ordine la catena di ereditarietà. Ad esempio, un blocco catch di tipo System. ArgumentException con una condizione di eccezione di accompagnamento può visualizzati prima ArgumentNullException più specifiche digitare anche se quest'ultimo deriva il primo. Questo è importante perché consente di scrivere una condizione di eccezione specifica che è associata a un tipo di eccezione generale seguito da un tipo di eccezione più specifico (con o senza una condizione di eccezione). Il comportamento di runtime rimane coerenza con le versioni precedenti del linguaggio c#. le eccezioni vengono intercettate dal primo blocco catch corrispondente. La complessità aggiuntiva è semplicemente che se corrisponde a un blocco catch è determinato dalla combinazione del tipo e la condizione di eccezione e il compilatore impone solo ordine blocchi catch senza condizioni di eccezione. Ad esempio, un catch(System.Exception) con una condizione di eccezione può apparire prima un catch(System.ArgumentException) con o senza una condizione di eccezione. Tuttavia, dopo un blocco catch per un'eccezione di tipo senza una condizione di eccezione viene visualizzato, non catch di un blocco di eccezioni più specifico (ad esempio catch(System.ArgumentNullException)) può verificarsi se dispone di una condizione di eccezione. In questo modo, il programmatore con "flessibilità" per le condizioni di eccezione codice potenzialmente ordinate, ovvero con intercettazione di eccezioni destinate per le successive condizioni di eccezione precedente, potenzialmente anche il rendering di quelle successive inavvertitamente raggiungibile. In definitiva, l'ordine dei blocchi catch è simile al modo in cui che si ordina istruzioni if-else. Una volta che la condizione viene soddisfatta, vengono ignorati tutti gli altri blocchi catch. A differenza delle condizioni in un'istruzione if-else, tuttavia, tutti i blocchi catch devono includere il controllo del tipo di eccezione.

Eccezioni le linee guida aggiornata

Nell'esempio di operatore di confronto di Figura 1 è un'operazione banale, ma non è limitata alla semplicità della condizione di eccezione. È possibile, ad esempio, creare un metodo chiamare per convalidare una condizione. L'unico requisito è che l'espressione è un predicato, viene restituito un valore booleano. In altre parole, è possibile eseguire essenzialmente qualsiasi codice all'interno della catena di chiamate di eccezione catch. Verrà visualizzata la possibilità di intercettazione mai nuovamente e rigenerare la stessa eccezione. essenzialmente, si è in grado di restringere il contesto sufficientemente prima di rilevare l'eccezione da intercettare solo quando è valido. In questo modo, le linee guida per evitare di rilevare le eccezioni che si sono grado di gestire completamente diventa realtà. Infatti, qualsiasi controllo condizionale un'istruzione throw vuoto circostante può probabilmente essere contrassegnata con un odore di codice ed evitato. È possibile aggiungere una condizione di eccezione a favore di dover utilizzare un'istruzione throw vuota tranne to mantenere uno stato volatile prima di un processo viene terminato.

Ciò detto, gli sviluppatori è consigliabile limitare le clausole condizionali per controllare solo il contesto. Questo è importante perché se l'espressione condizionale stesso genera un'eccezione, tale nuova eccezione verrà ignorata e la condizione viene considerata come false. Per questo motivo, è consigliabile evitare la generazione di eccezioni nell'espressione condizionale (eccezione).

Blocco Catch generale

In c# è necessario che qualsiasi oggetto che genera codice deve derivare da System. Exception. Tuttavia, questo requisito non universale per tutte le lingue. C/C++, ad esempio, consente qualsiasi tipo di oggetto generata, incluse le eccezioni gestite che non derivano da System. Exception o tipi primitivi anche come int o string. Se la derivazione da System. Exception o meno, a partire da c# 2.0, tutte le eccezioni verranno propagate in assembly c# derivate da System. Exception. Il risultato è che i blocchi catch System. Exception intercetterà tutte "ragionevolmente gestite" eccezioni non intercettate da blocchi precedenti. Prima di c# 1.0, tuttavia, se è stata generata un'eccezione non derivata da System. Exception da una chiamata al metodo (che risiede in un assembly non è stato scritto in c#), l'eccezione non rilevata da un blocco catch(System.Exception). Per questo motivo, in c# supporta anche un blocco catch generale (catch {}) che ora si comporta come il blocco catch (System. Exception exception) con la differenza che nessun nome di variabile o un tipo. Lo svantaggio di tale blocco è semplicemente che non vi è alcuna istanza di eccezione per l'accesso e pertanto non è possibile conoscere la linea di condotta appropriata. Non sarebbe anche possibile registrare l'eccezione o riconoscere caso improbabile in cui tale eccezione è irrilevante.

In pratica, il blocco di catch(System.Exception) e generale blocco catch, nel presente documento viene definito come System. Exception blocco catch, sono entrambi di evitare, ad eccezione in pretense di "gestione" dell'eccezione mediante l'accesso, prima di arrestare il processo. Seguendo il principio generale di sole eccezioni catch che è possibile gestire, potrebbe sembrare presumptuous per scrivere codice per il quale viene dichiarato il programmatore, questo catch può gestire tutte le eccezioni che possono essere generate. Innanzitutto, lo sforzo necessario per qualsiasi e tutte le eccezioni del catalogo (in particolare nel corpo del principale in cui la quantità di esecuzione di codice è il più elevato e contesto probabilmente il minor) sembra portare ad eccezione dei programmi più semplici. In secondo luogo, esiste una serie di possibili eccezioni che possono essere generate in modo imprevisto.

Prima di c# 4.0 esisteva una terza serie di danneggiato eccezioni di stato per il quale un programma non è stato anche in genere ripristinabile. Questo set è importante a partire da c# 4.0, tuttavia, poiché l'intercettazione di System. Exception (o un blocco catch generale) non infatti intercetterà tali eccezioni. (Tecnicamente è possibile decorare un metodo con il System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions in modo che anche queste eccezioni vengono intercettate, ma la probabilità che è possibile indirizzare sufficientemente tali eccezioni è estremamente difficile. Vedere bit.ly/1FgeCU6 per ulteriori informazioni.)

È un tecnicità notare le eccezioni di stato danneggiato che verrà solo passare catch in System. Exception blocchi quando generata dal runtime. Un generare esplicito di un'eccezione di stato danneggiato, ad esempio System. StackOverflowException o altra SystemException infatti essere intercettato. Tuttavia, generando un'eccezione, ad sarebbe molto fuorviante ed è supportato solo per motivi di compatibilità con le versioni precedenti. Linee guida di oggi non viene generata alcuna eccezione stato danneggiato (inclusi StackOverflowException, System. SystemException, System. OutOfMemoryException, COMException, SEHException e System. ExecutionEngineException).

In sintesi, evitare l'utilizzo di un blocco catch System. Exception blocco a meno che non deve per gestire l'eccezione con un codice di pulitura e registrazione eccezione prima di rigenerare o normalmente arresto dell'applicazione. Ad esempio, se il blocco catch è possibile salvare correttamente tutti i dati volatili (qualcosa che non è necessariamente possibile presupporre che, troppo, potrebbe essere danneggiato) prima dell'arresto dell'applicazione o rigenerare l'eccezione. In presenza di uno scenario per il quale l'applicazione deve terminare perché non è sicuro per continuare l'esecuzione di codice deve chiamare il metodo System.Environment.FailFast. Evitare blocchi catch generale tranne per registro normalmente l'eccezione prima di arrestare l'applicazione e System. Exception.

Avvolgendo

In questo articolo ho fornito le linee guida aggiornate per la gestione delle eccezioni, intercettazione di eccezioni, gli aggiornamenti causati da miglioramenti in c# e .NET Framework che si sono verificati negli ultimi diverse versioni. Nonostante il fatto che non vi sono alcune linee guida di nuovo, molti sono ancora semplicemente come confermati come prima. Di seguito è riportato un riepilogo delle linee guida per l'intercettazione di eccezioni:

  • EVITARE di rilevare le eccezioni che si sono grado di gestire completamente.
  • EVITARE eccezioni nascondere, eliminando, che gestire completamente.
  • Utilizzare throw per generare nuovamente un'eccezione. anziché generare < oggetto eccezione > all'interno di un blocco catch.
  • Se non eseguire pertanto espone dati privati, impostare proprietà di InnerException dell'eccezione di ritorno a capo con l'eccezione rilevata.
  • PROVARE a favore di dover generare nuovamente un'eccezione dopo avere acquisito uno che non sarà possibile gestire una condizione di eccezione.
  • EVITARE di generare le eccezioni generate dall'espressione condizionale (eccezione).
  • Prestare attenzione durante la rigenerazione di eccezioni diverse.
  • Raramente utilizzare System. Exception e blocchi catch generale, ad eccezione alla registrazione dell'eccezione prima di arrestare l'applicazione.
  • EVITARE di creare rapporti o di registrazione inferiore nello stack di chiamate di eccezione.

Andare a itl.tc/ExceptionGuidelinesForCSharp per un'analisi dei dettagli di ciascuno di questi. In un futuro articolo intendo concentrerà principalmente alle linee guida per la generazione di eccezioni. È sufficiente verrà to dire per ora che è un tema per la generazione di eccezioni: Il destinatario di un'eccezione è un programmatore piuttosto che l'utente finale di un programma.

Si noti che la maggior parte di questo materiale è tratto dal prossima edizione del mio libro, "Essential c# 6.0 (versione 5)" (Addison-Wesley, 2015), che è ora disponibile all'indirizzo itl.tc/EssentialCSharp.


Mark Michaelisè fondatore di IntelliTect, dove ha serve come responsabile dell'architettura tecnica e istruttore. Per quasi due decenni lavora un Microsoft MVP e un Microsoft Regional Director poiché 2007. Michaelis serve in diversi software progettazione revisione team Microsoft, tra cui c#, Microsoft Azure, SharePoint e Visual Studio ALM. Ha come relatore a conferenze per gli sviluppatori e ha scritto numerosi libri, tra cui il suo più recente, "Essential c# 6.0 (versione 5)." È possibile contattarlo su Facebook al facebook.com/Mark.Michaelis, sul suo blog all'indirizzo IntelliTect.com/Mark, su Twitter: @markmichaelis o tramite posta elettronica a mark@IntelliTect.com.

Grazie ai seguenti esperti tecnici per la revisione di questo articolo: Kevin Bost, Jason Peterson e Mads Torgerson