Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
La gestione corretta delle eccezioni è essenziale per l'affidabilità dell'applicazione. Puoi gestire intenzionalmente le eccezioni previste per evitare che l'app si arresti in modo anomalo. Tuttavia, un'app arrestata in modo anomalo è più affidabile e diagnosticabile rispetto a un'app con un comportamento indefinito.
Questo articolo descrive le procedure consigliate per la gestione e la creazione di eccezioni.
Gestione delle eccezioni
Le procedure consigliate seguenti riguardano la gestione delle eccezioni:
- Usare i blocchi try/catch/finally per eseguire il ripristino da errori o rilasciare risorse
- Gestire le condizioni comuni per evitare eccezioni
- Gestire l'annullamento e le eccezioni asincrone
- classi di progettazione in modo che le eccezioni possano essere evitate
- Stato di ripristino quando i metodi non vengono completati a causa di eccezioni
- Catturare e rilanciare correttamente le eccezioni
Usare i blocchi try/catch/finally per eseguire il ripristino da errori o rilasciare risorse
Per il codice che può potenzialmente generare un'eccezione, e quando l'app può eseguire il ripristino da tale eccezione, usare blocchi try
/catch
intorno al codice. Nei blocchi catch
ordinare sempre le eccezioni dalla più derivata alla meno derivata. Tutte le eccezioni derivano dalla classe Exception. Altre eccezioni derivate non vengono gestite da una clausola catch
preceduta da una clausola catch
per una classe di eccezione di base. Quando il codice non riesce a eseguire il ripristino da un'eccezione, non intercettare l'eccezione. Abilitare i metodi più avanti nello stack di chiamate per il ripristino, se possibile.
Pulire le risorse allocate con istruzioni using
o blocchi finally
. Preferire le istruzioni using
per pulire automaticamente le risorse quando vengono generate eccezioni. Usare i blocchi finally
per pulire le risorse che non implementano IDisposable. Il codice in una clausola finally
viene quasi sempre eseguito anche quando vengono generate eccezioni.
Gestire le condizioni comuni per evitare eccezioni
Per le condizioni che potrebbero verificarsi, ma potrebbero attivare un'eccezione, valutare la possibilità di gestirle in modo da evitare l'eccezione. Ad esempio, se si tenta di chiudere una connessione già chiusa, si otterrà un InvalidOperationException
. È possibile evitare che usando un'istruzione if
per controllare lo stato della connessione prima di tentare di chiuderla.
if (conn.State != ConnectionState.Closed)
{
conn.Close();
}
If conn.State <> ConnectionState.Closed Then
conn.Close()
End IF
Se non si controlla lo stato della connessione prima della chiusura, è possibile intercettare l'eccezione InvalidOperationException
.
try
{
conn.Close();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.GetType().FullName);
Console.WriteLine(ex.Message);
}
Try
conn.Close()
Catch ex As InvalidOperationException
Console.WriteLine(ex.GetType().FullName)
Console.WriteLine(ex.Message)
End Try
L'approccio da scegliere dipende dalla frequenza con cui si prevede che si verifichi l'evento.
Usare la gestione delle eccezioni se l'evento non si verifica spesso, ovvero se l'evento è veramente eccezionale e indica un errore, ad esempio una fine del file imprevista. Quando si usa la gestione delle eccezioni, meno codice viene eseguito in condizioni normali.
Verificare la presenza di condizioni di errore nel codice se l'evento si verifica regolarmente e può essere considerato parte della normale esecuzione. Quando si verifica la presenza di condizioni di errore comuni, viene eseguita una minore quantità di codice perché si evitano eccezioni.
Nota
I controlli iniziali eliminano la maggior parte delle eccezioni. Tuttavia, possono verificarsi condizioni di gara in cui la condizione sorvegliata cambia tra il controllo e l'operazione e, in tal caso, è possibile incontrare comunque un'eccezione.
Chiamare i metodi Try*
per evitare eccezioni
Se il costo delle prestazioni delle eccezioni è proibitivo, alcuni metodi della libreria .NET forniscono forme alternative di gestione degli errori. Ad esempio, Int32.Parse genera un OverflowException se il valore da analizzare è troppo grande per essere rappresentato da Int32. Tuttavia, Int32.TryParse non genera questa eccezione. Restituisce invece un valore Boolean e ha un parametro out
che contiene l'intero valido analizzato in caso di esito positivo.
Dictionary<TKey,TValue>.TryGetValue ha un comportamento simile per tentare di ottenere un valore da un dizionario.
Intercettare l'annullamento e le eccezioni asincrone
È preferibile intercettare OperationCanceledException anziché TaskCanceledException, che deriva da OperationCanceledException
, quando si chiama un metodo asincrono. Molti metodi asincroni generano un'eccezione OperationCanceledException se viene richiesto l'annullamento. Queste eccezioni consentono l'interruzione efficiente dell'esecuzione e lo stack delle chiamate viene smontato una volta che viene osservata una richiesta di annullamento.
I metodi asincroni archiviano le eccezioni generate durante l'esecuzione nell'attività che restituiscono. Se un'eccezione viene archiviata nell'attività restituita, tale eccezione verrà generata quando l'attività è attesa. Le eccezioni di utilizzo, ad esempio ArgumentException, vengono comunque generate in modo sincrono. Per ulteriori informazioni, vedere eccezioni asincrone.
Progettare classi in modo che le eccezioni possano essere evitate
Una classe può fornire metodi o proprietà che consentono di evitare di effettuare una chiamata che attiverebbe un'eccezione. Ad esempio, la classe FileStream fornisce metodi che consentono di determinare se la fine del file è stata raggiunta. È possibile chiamare questi metodi per evitare l'eccezione generata se si legge oltre la fine del file. L'esempio seguente illustra come leggere fino alla fine di un file senza attivare un'eccezione:
class FileRead
{
public static void ReadAll(FileStream fileToRead)
{
ArgumentNullException.ThrowIfNull(fileToRead);
int b;
// Set the stream position to the beginning of the file.
fileToRead.Seek(0, SeekOrigin.Begin);
// Read each byte to the end of the file.
for (int i = 0; i < fileToRead.Length; i++)
{
b = fileToRead.ReadByte();
Console.Write(b.ToString());
// Or do something else with the byte.
}
}
}
Class FileRead
Public Sub ReadAll(fileToRead As FileStream)
' This if statement is optional
' as it is very unlikely that
' the stream would ever be null.
If fileToRead Is Nothing Then
Throw New System.ArgumentNullException()
End If
Dim b As Integer
' Set the stream position to the beginning of the file.
fileToRead.Seek(0, SeekOrigin.Begin)
' Read each byte to the end of the file.
For i As Integer = 0 To fileToRead.Length - 1
b = fileToRead.ReadByte()
Console.Write(b.ToString())
' Or do something else with the byte.
Next i
End Sub
End Class
Un altro modo per evitare eccezioni consiste nel restituire null
(o impostazione predefinita) per i casi di errore più comuni anziché generare un'eccezione. Un caso di errore comune può essere considerato un normale flusso di controllo. Restituendo null
(o impostazione predefinita) in questi casi, si riduce al minimo l'impatto sulle prestazioni di un'app.
Per i tipi di valore, valutare se utilizzare Nullable<T>
o default
come indicatore di errore per la propria applicazione. Usando Nullable<Guid>
, default
diventa null
anziché Guid.Empty
. In alcuni casi, l'aggiunta di Nullable<T>
può rendere più chiaro quando un valore è presente o assente. In altri casi, l'aggiunta di Nullable<T>
può creare casi aggiuntivi per verificare che non siano necessari e servire solo per creare potenziali fonti di errori.
Ripristinare lo stato quando i metodi non vengono completati a causa di eccezioni
I chiamanti devono essere in grado di presupporre che non ci siano effetti collaterali quando viene generata un'eccezione da un metodo. Ad esempio, se hai codice che trasferisce denaro ritirando da un conto e depositando in un altro conto e viene generata un'eccezione durante l'esecuzione del deposito, non vuoi che il prelievo rimanga attivo.
public void TransferFunds(Account from, Account to, decimal amount)
{
from.Withdrawal(amount);
// If the deposit fails, the withdrawal shouldn't remain in effect.
to.Deposit(amount);
}
Public Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)
from.Withdrawal(amount)
' If the deposit fails, the withdrawal shouldn't remain in effect.
[to].Deposit(amount)
End Sub
Il metodo precedente non genera direttamente eccezioni. Tuttavia, è necessario scrivere il metodo in modo che il ritiro venga invertito se l'operazione di deposito non riesce.
Un modo per gestire questa situazione è rilevare eventuali eccezioni generate dalla transazione di deposito e annullare il prelievo.
private static void TransferFunds(Account from, Account to, decimal amount)
{
string withdrawalTrxID = from.Withdrawal(amount);
try
{
to.Deposit(amount);
}
catch
{
from.RollbackTransaction(withdrawalTrxID);
throw;
}
}
Private Shared Sub TransferFunds(from As Account, [to] As Account, amount As Decimal)
Dim withdrawalTrxID As String = from.Withdrawal(amount)
Try
[to].Deposit(amount)
Catch
from.RollbackTransaction(withdrawalTrxID)
Throw
End Try
End Sub
In questo esempio viene illustrato l'uso di throw
per rigenerare l'eccezione originale, rendendo più semplice per i chiamanti visualizzare la causa reale del problema senza dover esaminare la proprietà InnerException. Un'alternativa consiste nel generare una nuova eccezione e includere l'eccezione originale come eccezione interna.
catch (Exception ex)
{
from.RollbackTransaction(withdrawalTrxID);
throw new TransferFundsException("Withdrawal failed.", innerException: ex)
{
From = from,
To = to,
Amount = amount
};
}
Catch ex As Exception
from.RollbackTransaction(withdrawalTrxID)
Throw New TransferFundsException("Withdrawal failed.", innerException:=ex) With
{
.From = from,
.[To] = [to],
.Amount = amount
}
End Try
Acquisire e rigenerare correttamente le eccezioni
Una volta generata un'eccezione, parte delle informazioni che contiene è la traccia dello stack. L'analisi dello stack è un elenco della gerarchia di chiamate al metodo che inizia con il metodo che genera l'eccezione e termina con il metodo che intercetta l'eccezione. Se si rigenera un'eccezione specificando l'eccezione nell'istruzione throw
, ad esempio throw e
, l'analisi dello stack viene riavviata nel metodo corrente e l'elenco di chiamate al metodo tra il metodo originale che ha generato l'eccezione e il metodo corrente viene perso. Per mantenere le informazioni della traccia dello stack originali con l'eccezione, esistono due opzioni che dipendono dalla posizione in cui si sta rilanciando l'eccezione.
- Se si rigenera l'eccezione dall'interno del gestore che ha intercettato l'istanza dell'eccezione (blocco
catch
), usare l'istruzionethrow
senza specificare l'eccezione. La regola di analisi del codice CA2200 consente di trovare posizioni nel codice in cui è possibile perdere accidentalmente le informazioni di analisi dello stack. - Se si sta rilanciando l'eccezione da un punto diverso dal gestore (il blocco
catch
), utilizzare ExceptionDispatchInfo.Capture(Exception) per acquisire l'eccezione nel gestore e ExceptionDispatchInfo.Throw() quando si vuole rilanciarla. È possibile utilizzare la proprietà ExceptionDispatchInfo.SourceException per esaminare l'eccezione acquisita.
Nell'esempio seguente viene illustrato come usare la classe ExceptionDispatchInfo e l'aspetto dell'output.
ExceptionDispatchInfo? edi = null;
try
{
var txt = File.ReadAllText(@"C:\temp\file.txt");
}
catch (FileNotFoundException e)
{
edi = ExceptionDispatchInfo.Capture(e);
}
// ...
Console.WriteLine("I was here.");
if (edi is not null)
edi.Throw();
Se il file nel codice di esempio non esiste, viene generato l'output seguente:
I was here.
Unhandled exception. System.IO.FileNotFoundException: Could not find file 'C:\temp\file.txt'.
File name: 'C:\temp\file.txt'
at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options)
at Microsoft.Win32.SafeHandles.SafeFileHandle.Open(String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
at System.IO.Strategies.OSFileStreamStrategy..ctor(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
at System.IO.Strategies.FileStreamHelpers.ChooseStrategyCore(String path, FileMode mode, FileAccess access, FileShare share, FileOptions options, Int64 preallocationSize, Nullable`1 unixCreateMode)
at System.IO.StreamReader.ValidateArgsAndOpenPath(String path, Encoding encoding, Int32 bufferSize)
at System.IO.File.ReadAllText(String path, Encoding encoding)
at Example.ProcessFile.Main() in C:\repos\ConsoleApp1\Program.cs:line 12
--- End of stack trace from previous location ---
at Example.ProcessFile.Main() in C:\repos\ConsoleApp1\Program.cs:line 24
Lancio di eccezioni
Le procedure consigliate seguenti riguardano il modo in cui vengono generate eccezioni:
- Usare tipi di eccezione predefiniti
- Usare i metodi del generatore di eccezioni
- Includere un messaggio di stringa localizzato
- Usare una grammatica appropriata
- Posizionare bene le istruzioni throw
- Non generare eccezioni nelle clausole finally
- Non generare eccezioni da posizioni impreviste
- generare eccezioni di convalida degli argomenti in modo sincrono
Usare tipi di eccezione predefiniti
Introdurre una nuova classe di eccezione solo quando non è applicabile una classe predefinita. Per esempio:
- Se una chiamata a un set di proprietà o a un metodo non è appropriata in base allo stato corrente dell'oggetto, generare un'eccezione InvalidOperationException.
- Se vengono passati parametri non validi, generare un'eccezione ArgumentException o una delle classi predefinite che derivano da ArgumentException.
Nota
Sebbene sia consigliabile usare tipi di eccezione predefiniti quando possibile, non è consigliabile generare alcuni tipi di eccezione riservati, ad esempio AccessViolationException, IndexOutOfRangeException, NullReferenceException e StackOverflowException. Per altre informazioni, vedere CA2201: Non generare tipi di eccezione riservati.
Usare i metodi del generatore di eccezioni
È comune che una classe generi la stessa eccezione da posizioni diverse nell'implementazione. Per evitare un numero eccessivo di codice, creare un metodo helper che crea l'eccezione e lo restituisce. Per esempio:
class FileReader
{
private readonly string _fileName;
public FileReader(string path)
{
_fileName = path;
}
public byte[] Read(int bytes)
{
byte[] results = FileUtils.ReadFromFile(_fileName, bytes) ?? throw NewFileIOException();
return results;
}
static FileReaderException NewFileIOException()
{
string description = "My NewFileIOException Description";
return new FileReaderException(description);
}
}
Class FileReader
Private fileName As String
Public Sub New(path As String)
fileName = path
End Sub
Public Function Read(bytes As Integer) As Byte()
Dim results() As Byte = FileUtils.ReadFromFile(fileName, bytes)
If results Is Nothing
Throw NewFileIOException()
End If
Return results
End Function
Function NewFileIOException() As FileReaderException
Dim description As String = "My NewFileIOException Description"
Return New FileReaderException(description)
End Function
End Class
Alcuni importanti tipi di eccezione .NET dispongono di metodi statici di helper throw
che allocano ed espongono l'eccezione. È consigliabile chiamare questi metodi invece di costruire e generare il tipo di eccezione corrispondente:
- ArgumentNullException.ThrowIfNull
- ArgumentException.ThrowIfNullOrEmpty(String, String)
- ArgumentException.ThrowIfNullOrWhiteSpace(String, String)
- ArgumentOutOfRangeException.ThrowIfZero<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfNegative<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfLessThan<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfNotEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfNegativeOrZero<T>(T, String)
- ArgumentOutOfRangeException.ThrowIfGreaterThan<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfLessThanOrEqual<T>(T, T, String)
- ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual<T>(T, T, String)
- ObjectDisposedException.ThrowIf
Suggerimento
Le regole di analisi del codice seguenti consentono di trovare posizioni nel codice in cui è possibile sfruttare questi helper statici throw
: CA1510, CA1511, CA1512e CA1513.
Se implementate un metodo asincrono, chiamate CancellationToken.ThrowIfCancellationRequested() invece di verificare se è stato richiesto l'annullamento e quindi costruire e lanciare OperationCanceledException. Per altre informazioni, vedere CA2250.
Includere un messaggio di stringa localizzato
Il messaggio di errore visualizzato dall'utente deriva dalla proprietà Exception.Message dell'eccezione generata e non dal nome della classe di eccezione. In genere, si assegna un valore alla proprietà Exception.Message passando la stringa del messaggio all'argomento message
di un costruttore exception .
Per le applicazioni localizzate, è necessario fornire una stringa di messaggio localizzata per ogni eccezione che l'applicazione può generare. I file di risorse vengono usati per fornire messaggi di errore localizzati. Per informazioni sulla localizzazione delle applicazioni e sul recupero di stringhe localizzate, vedere gli articoli seguenti:
- Procedura: Creare eccezioni definite dall'utente con messaggi di eccezione localizzati
- Risorse nelle app .NET
- System.Resources.ResourceManager
Usare la grammatica appropriata
Scrivere frasi chiare e includere la punteggiatura finale. Ogni frase nella stringa assegnata alla proprietà Exception.Message deve terminare con il punto. Ad esempio, "La tabella di log ha tracimato." Usa la grammatica e la punteggiatura corrette.
Posizionare correttamente le istruzioni throw
Posizionare istruzioni throw nei punti in cui il traceback dello stack sarà utile. L'analisi dello stack inizia con l'istruzione in cui viene generata l'eccezione e termina con l'istruzione catch
che intercetta l'eccezione.
Non generare eccezioni nelle clausole finally
Non sollevare eccezioni nelle clausole finally
. Per altre informazioni, vedere Regola di analisi del codice CA2219.
Non generare eccezioni da posizioni impreviste
Alcuni metodi, ad esempio Equals
, GetHashCode
e ToString
metodi, costruttori statici e operatori di uguaglianza, non devono generare eccezioni. Per altre informazioni, vedere Regola di analisi del codice CA1065.
Generare eccezioni di convalida degli argomenti in modo sincrono
Nei metodi che restituiscono attività è necessario convalidare gli argomenti e generare eventuali eccezioni corrispondenti, ad esempio ArgumentException e ArgumentNullException, prima di immettere la parte asincrona del metodo. Le eccezioni generate nella parte asincrona del metodo vengono archiviate nell'attività restituita e non emergono fino a quando, ad esempio, l'attività non viene attesa. Per maggiori informazioni, vedere Eccezioni nei metodi che restituiscono attività .
Tipi di eccezione personalizzati
Le procedure consigliate seguenti riguardano i tipi di eccezione personalizzati:
-
I nomi delle classi di eccezioni terminano con
Exception
- Includere tre costruttori
- Specificare proprietà aggiuntive in base alle esigenze
Termina i nomi delle classi di eccezione con Exception
Quando è necessaria un'eccezione personalizzata, denominarla in modo appropriato e derivarla dalla classe Exception. Per esempio:
public class MyFileNotFoundException : Exception
{
}
Public Class MyFileNotFoundException
Inherits Exception
End Class
Includere tre costruttori
Usare almeno i tre costruttori comuni quando si creano classi di eccezione personalizzate: il costruttore senza parametri, un costruttore che accetta un messaggio stringa e un costruttore che accetta un messaggio stringa e un'eccezione interna.
- Exception(), che usa i valori predefiniti.
- Exception(String), che accetta un messaggio di tipo stringa.
- Exception(String, Exception), che accetta una stringa di messaggio e un'eccezione interna.
Per un esempio, vedere Procedura: Creare eccezioni definite dall'utente.
Specificare proprietà aggiuntive in base alle esigenze
Specificare proprietà aggiuntive per un'eccezione (oltre alla stringa di messaggio personalizzata) solo quando è presente uno scenario programmatico in cui sono utili le informazioni aggiuntive. Ad esempio, il FileNotFoundException fornisce la proprietà FileName.