Beren.it

All that you can leave behind

Throw e Throw ex: le differenze

Nell'ambito della gestione delle eccezioni con il framework .NET è opportuno spendere due parole per capire come, in linea di massima, un'eccezione debba venire gestita e le modalità per farlo. Tutti sanno che per gestire un'eccezione durante l'esecuzione di un frammento di codice occorre inserirlo all'interno di un blocco try-catch. In questo modo al verificarsi un eccezione potremmo eseguire del codice dedicato in base anche alla tipologia dell'eccezione stessa. Calandoci però in un caso particolare voglio parlare di una parte specifica della gestione delle eccezioni definita "exception bubbling".

"Exception bubbling" è un'espressione abbastanza intraducibile che si riferisce alla possibilità di risollevare l'eccezione dopo aver eseguito le operazioni custom nel catch, per consentire di lavorare con l'eccezione anche esternamente al metodo in cui l'eccezione è avvenuta. Ed è proprio a questo proposito che qualora non si tenga in considerazione di alcune accortezze si potrebbe incappare in problematiche non di poco conto in fase di debugging.

Si prenda ad esempio in considerazione il frammento di codice qua sotto:

[C#]

try
{
    // esecuzione di una qualche operazione che fallisce
}
catch (Exception ex)
{
    // effettuare un cleanup
    throw ex;
}

Questo è certamente uno dei casi più comuni nella gestione di un eccezione: c'è un parte dedicata alla gestione dell'eccezione (detta cleanup) e quindi, al termine l'eccezione viene rilanciata.

E' un pò meno comune invece vedere qualcosa di simile:

[C#]

try
{
    // esecuzione di una qualche operazione che fallisce
}
catch (Exception ex)
{
    // effettuare un cleanup
    throw;
}

All'apparenza queste due gestioni sembrano molto simili in realtà nascondono una notevole differenza per quanto riguarda le informazioni dello Stack Trace. In pratica analizzando in debug le due situazioni si scopre che nel primo caso lo stack trace verrà troncato al di sotto del metodo in cui è raccolto il catch con il risultato che sembrerà che l'eccezione sia stata generata proprio dal vostro codice. Infatti in questo modo è come se screaste una vostra propria eccezione.

Utilizzare invece il semplice "throw" vi consentirà di preservare tutte le info dello stack. Tutto ciò è ancora più chiaro se si guardasse il codice IL generato che a fronte di un "throw" del primo caso userà un "rethrow" nel secondo.

Questo però non significa affatto che in generale il "throw ex" non debba mai venire utilizzato. Anzi, qualora si vogliano aggiungere informazioni aggiuntive oppure generare un nuova eccezione personalizzata è opportuno utilizzare questo approcio con l'unica accortezza, onde evitare il problema del troncamento dello stack, di passare l'eccezione originale nel costruttore della nuova in modo da salvare i dati dello stack all'interno dell'inner exception:

[C#]

try
{
    // esecuzione di una qualche operazione che fallisce
}
catch (Exception ex)
{
    // effettuare un cleanup
    throw new ApplicationException("L'operazione è fallita!", ex);
}

A conclusione di questa breve analisi elenco tre punti che possono essere utili nella gestione delle eccezioni:

  1. Catturare solo le eccezioni per le quali si debba effettuare una sorta di cleanup e che abbiano una certa importanza
  2. Per alzare nuovamente l'eccezione usate il throw
  3. Se doveste generare una nuova eccezione personalizzata oppure aggiungere qualche informazione a quella originale ricordatevi sempre di passare anch'essa onde perdere le info dello stack.