Usare le eccezioni
In C# gli errori del programma in fase di esecuzione vengono propagati attraverso il programma usando il meccanismo delle eccezioni. Le eccezioni vengono generate dal codice che rileva un errore e intercettate dal codice in grado di correggere l'errore. Le eccezioni possono essere generate dal runtime .NET o dal codice in un programma. Quando viene generata un'eccezione, si propaga nello stack di chiamate finché non viene trovata un'istruzione catch
per l'eccezione. Le eccezioni non rilevate vengono gestite da un gestore di eccezioni generico del sistema che visualizza una finestra di dialogo.
Le eccezioni sono rappresentate dalle classi derivate da Exception. Questa classe identifica il tipo di eccezione e contiene proprietà con informazioni sull'eccezione. Generare un'eccezione implica la creazione un'istanza di una classe derivata dall'eccezione, la configurazione, se necessaria, delle proprietà dell'eccezione e la generazione dell'oggetto usando la parola chiave throw
. Ad esempio:
class CustomException : Exception
{
public CustomException(string message)
{
}
}
private static void TestThrow()
{
throw new CustomException("Custom exception in TestThrow()");
}
Dopo la generazione di un'eccezione, il runtime controlla l'istruzione corrente per verificare se si trova all'interno di un blocco try
. In questo caso tutti i blocchi catch
associati al blocco try
vengono controllati per verificare se è possibile intercettare l'eccezione. I blocchi Catch
normalmente specificano i tipi di eccezione. Se il tipo del blocco catch
è lo stesso tipo dell'eccezione o di una classe di base dell'eccezione, il blocco catch
può gestire il metodo. Ad esempio:
try
{
TestThrow();
}
catch (CustomException ex)
{
System.Console.WriteLine(ex.ToString());
}
Se l'istruzione che genera un'eccezione non è in un blocco try
oppure se il blocco try
che la contiene non corrisponde ad alcun blocco catch
, il runtime controlla il metodo di chiamata per un'istruzione try
e i blocchi catch
. Il runtime continua a risalire lo stack di chiamate alla ricerca di un blocco catch
compatibile. Quando il blocco catch
viene trovato ed eseguito, il controllo passa all'istruzione successiva dopo quel blocco catch
.
Un'istruzione try
può contenere più di un blocco catch
. Viene eseguita la prima istruzione catch
in grado di gestire l'eccezione ed eventuali istruzionicatch
successive, anche se compatibili, vengono ignorate. Ordinare i blocchi catch dal più specifico, o più derivato, al meno specifico. Ad esempio:
using System;
using System.IO;
namespace Exceptions
{
public class CatchOrder
{
public static void Main()
{
try
{
using (var sw = new StreamWriter("./test.txt"))
{
sw.WriteLine("Hello");
}
}
// Put the more specific exceptions first.
catch (DirectoryNotFoundException ex)
{
Console.WriteLine(ex);
}
catch (FileNotFoundException ex)
{
Console.WriteLine(ex);
}
// Put the least specific exception last.
catch (IOException ex)
{
Console.WriteLine(ex);
}
Console.WriteLine("Done");
}
}
}
Prima dell'esecuzione del blocco catch
il runtime cerca i blocchi finally
. I blocchi Finally
consentono al programmatore di eliminare eventuali stati ambigui che possono essere rimasti dopo un blocco try
interrotto o di rilasciare eventuali risorse esterne, ad esempio handle di grafica, connessioni al database o flussi di file, senza attendere che il Garbage Collector del runtime finalizzi gli oggetti. Ad esempio:
static void TestFinally()
{
FileStream? file = null;
//Change the path to something that works on your machine.
FileInfo fileInfo = new System.IO.FileInfo("./file.txt");
try
{
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Closing the file allows you to reopen it immediately - otherwise IOException is thrown.
file?.Close();
}
try
{
file = fileInfo.OpenWrite();
Console.WriteLine("OpenWrite() succeeded");
}
catch (IOException)
{
Console.WriteLine("OpenWrite() failed");
}
}
Se WriteByte()
ha generato un'eccezione, il codice nel secondo blocco try
che tenti di riaprire il file avrebbe esito negativo se non viene chiamata file.Close()
e il file rimarrebbe bloccato. Poiché i blocchi finally
vengono eseguiti anche se viene generata un'eccezione, il blocco finally
dell'esempio precedente consente di chiudere correttamente il file e di evitare un errore.
Se non viene trovato alcun blocco catch
compatibile nello stack di chiamate dopo la generazione di un'eccezione, si verifica una delle tre situazioni seguenti:
- Se l'eccezione è all'interno di un finalizzatore, il finalizzatore viene interrotto e viene chiamato il finalizzatore di base, se presente.
- Se lo stack di chiamate contiene un costruttore statico o un inizializzatore di campo statico, viene generata TypeInitializationException con l'eccezione originale assegnata alla proprietà InnerException della nuova eccezione.
- Se viene raggiunto l'inizio del thread, il thread viene terminato.