Procedure consigliate per le eccezioni
Un'applicazione progettata correttamente gestisce eccezioni ed errori per impedire arresti anomali dell'applicazione. In questo articolo vengono descritte le procedure consigliate per la gestione e la creazione di eccezioni.
Usare blocchi try/catch/finally per correggere errori o rilasciare risorse
Usare i blocchi try
/catch
intorno al codice che può potenzialmente generare un'eccezione e che il codice non può correggere. Nei blocchi catch
ordinare sempre le eccezioni dalla più derivata alla meno derivata. Tutte le eccezioni derivano dalla classe Exception. Le eccezioni più derivate non vengono gestite da una clausola catch preceduta da una clausola catch per una classe di eccezione di base. Quando il codice non corregge un'eccezione, non recuperare l'eccezione. Abilitare i metodi nella parte superiore dello stack di chiamata per eseguire il ripristino, se possibile.
Pulire le risorse che sono 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 se vengono generate eccezioni.
Gestire le condizioni comuni senza generare eccezioni
È consigliabile gestire le condizioni che potrebbero verificarsi ma potrebbero generare un'eccezione in modo da evitare l'eccezione. Ad esempio, se si tenta di chiudere una connessione già chiusa, si otterrà InvalidOperationException
. Per impedire che ciò accada, usare un'istruzione if
per verificare lo stato della connessione prima di tentare di chiuderla.
if (conn->State != ConnectionState::Closed)
{
conn->Close();
}
if (conn.State != ConnectionState.Closed)
{
conn.Close();
}
If conn.State <> ConnectionState.Closed Then
conn.Close()
End IF
Se prima della chiusura non viene verificato lo stato della connessione, è possibile rilevare l'eccezione InvalidOperationException
.
try
{
conn->Close();
}
catch (InvalidOperationException^ ex)
{
Console::WriteLine(ex->GetType()->FullName);
Console::WriteLine(ex->Message);
}
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
Nella scelta del metodo è necessario considerare la frequenza con cui si prevede che l'evento possa verificarsi.
Usare la gestione delle eccezioni se l'evento non si verifica spesso, ovvero, se l'evento è davvero eccezionale e indica un errore quale la fine imprevista di un file. Quando si utilizza la gestione delle eccezioni, una minore quantità di codice viene eseguita in condizioni normali.
Ricercare le condizioni di errore nel codice se l'evento si verifica ripetutamente e può essere considerato parte dell'esecuzione normale. Quando si ricercano le condizioni di errore comuni, viene eseguita una minore quantità di codice poiché vengono evitate le eccezioni.
Progettare le classi in modo da evitare le eccezioni
Una classe può offrire metodi o proprietà che consentono di evitare di effettuare una chiamata che potrebbe generare un'eccezione. Con la classe FileStream, ad esempio, sono disponibili metodi che consentono di determinare se è stata raggiunta la fine del file. Questi metodi possono essere usati per evitare l'eccezione che verrebbe generata se si continua la lettura oltre la fine del file. L'esempio seguente illustra come continuare la lettura fino alla fine di un file senza generare un'eccezione:
class FileRead
{
public:
void ReadAll(FileStream^ fileToRead)
{
// This if statement is optional
// as it is very unlikely that
// the stream would ever be null.
if (fileToRead == nullptr)
{
throw gcnew System::ArgumentNullException();
}
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 void ReadAll(FileStream fileToRead)
{
// This if statement is optional
// as it is very unlikely that
// the stream would ever be null.
if (fileToRead == null)
{
throw new ArgumentNullException();
}
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 le eccezioni consiste nel restituire Null (o default) per i casi di errore più comuni, invece di generare un'eccezione. Un caso di errore comune può essere considerato come un normale flusso di controllo. Restituendo Null (o default) in questi casi, si riduce al minimo l'impatto sulle prestazioni di un'app.
Per i tipi di valore, la scelta tra Nullable<T>
o default come indicatore di errore è un aspetto da considerare in relazione all'app. Usando Nullable<Guid>
, default
diventa null
invece di Guid.Empty
. Talvolta, l'aggiunta di Nullable<T>
può indicare più chiaramente quando un valore è presente o assente. Altre volte, l'aggiunta di Nullable<T>
può creare casi aggiuntivi da controllare che non sono necessari e serve solo per creare potenziali fonti di errore.
Generare eccezioni anziché restituire un codice di errore
Le eccezioni garantiscono il rilevamento degli errori quando il codice chiamante non rileva un codice restituito.
Usare i tipi di eccezione .NET predefiniti
Introdurre una nuova classe di eccezioni solo quando non è possibile applicare una classe predefinita. Ad 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.
Terminare i nomi delle classi di eccezioni con la parola Exception
Quando è necessaria un'eccezione personalizzata, assegnare un nome appropriato all'eccezione e derivarla dalla classe Exception. Ad esempio:
public ref class MyFileNotFoundException : public Exception
{
};
public class MyFileNotFoundException : Exception
{
}
Public Class MyFileNotFoundException
Inherits Exception
End Class
Inserire tre costruttori nelle classi di eccezioni personalizzate
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 valori predefiniti.
- Exception(String), che accetta un messaggio stringa.
- Exception(String, Exception), che accetta un messaggio stringa e un'eccezione interna.
Per un esempio, vedere Procedura: Creare eccezioni definite dall'utente.
Garantire la disponibilità dei dati dell'eccezione per il codice eseguito in modalità remota
Quando si creano eccezioni definite dall'utente, garantire che i metadati relativi alle eccezioni siano disponibili per il codice eseguito in modalità remota.
Ad esempio, nelle implementazioni .NET che supportano domini app, è possibile che si verifichino eccezioni nei domini app. Si supponga che il dominio app A crei il dominio app B che esegue codice che genera un'eccezione. Per rilevare e gestire correttamente l'eccezione è necessario che il dominio app A sia in grado di individuare l'assembly contenente l'eccezione generata dal dominio app B. Se il dominio app B genera un'eccezione contenuta in un assembly nella relativa base dell'applicazione ma non nella base dell'applicazione del dominio app A, il dominio app A non sarà in grado di individuare l'eccezione e Common Language Runtime genererà un'eccezione FileNotFoundException. Per evitare che questo si verifichi è possibile distribuire l'assembly contenente le informazioni sull'eccezione in due modi:
- Inserendo l'assembly in una base applicativa comune condivisa da entrambi i domini applicazione
- Se i domini non condividono alcuna base applicativa comune, firmando l'assembly contenente le informazioni sull'eccezione con un nome sicuro e distribuendo tale assembly nella Global Assembly Cache.
Usare messaggi di errore grammaticalmente corretti
Scrivere frasi chiare e includere la punteggiatura finale. Ogni frase della stringa assegnata alla proprietà Exception.Message deve terminare con un punto. Ad esempio, "La tabella di log ha superato l'overflow." è una stringa di messaggio appropriata.
Includere in ogni eccezione un messaggio stringa localizzato
Il messaggio di errore visualizzato all'utente è derivato 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 di eccezione.
Per le applicazioni localizzate, è necessario specificare una stringa di messaggio localizzata per ogni eccezione che può essere generata dall'applicazione. Usare i file di risorse per specificare i 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
Nelle eccezioni personalizzate specificare le proprietà aggiuntive necessarie
Specificare le proprietà aggiuntive di un'eccezione (oltre alla stringa del messaggio personalizzata) solo in un contesto di codice in cui è utile avere a disposizione altre informazioni. Ad esempio, FileNotFoundException fornisce la proprietà FileName.
Inserire istruzioni throw in modo che l'analisi dello stack risulti utile
La traccia dello stack inizia in corrispondenza dell'istruzione in cui l'eccezione viene generata e termina in corrispondenza dell'istruzione catch
che intercetta l'eccezione.
Usare metodi per la creazione di eccezioni
Una classe genera spesso la stessa eccezione da punti diversi dell'implementazione. Per evitare codice di dimensioni eccessive, utilizzare metodi di supporto che creano l'eccezione e la restituiscono. Ad esempio:
ref class FileReader
{
private:
String^ fileName;
public:
FileReader(String^ path)
{
fileName = path;
}
array<Byte>^ Read(int bytes)
{
array<Byte>^ results = FileUtils::ReadFromFile(fileName, bytes);
if (results == nullptr)
{
throw NewFileIOException();
}
return results;
}
FileReaderException^ NewFileIOException()
{
String^ description = "My NewFileIOException Description";
return gcnew FileReaderException(description);
}
};
class FileReader
{
private string fileName;
public FileReader(string path)
{
fileName = path;
}
public byte[] Read(int bytes)
{
byte[] results = FileUtils.ReadFromFile(fileName, bytes);
if (results == null)
{
throw NewFileIOException();
}
return results;
}
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
In alcuni casi è preferibile usare il costruttore dell'eccezione per compilare l'eccezione. Un esempio è una classe di eccezioni globali, ad esempio ArgumentException.
Ripristinare lo stato quando i metodi non sono completi a causa di eccezioni
I chiamanti dovrebbero avere la garanzia che non si verifichino effetti secondari quando un'eccezione viene generata da un metodo. Ad esempio, se è presente un codice che trasferisce denaro prelevandolo da un conto e depositandolo in un altro conto e viene generata un'eccezione durante l'esecuzione del deposito, non si desidera 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 consiste nel 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
Questo esempio illustra l'uso di throw
per generare nuovamente l'eccezione originale rendendo in questo modo più semplice per i chiamanti visualizzare la causa effettiva del problema senza dover esaminare la proprietà InnerException. In alternativa è possibile 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 le eccezioni da rigenerare in un secondo momento
Per acquisire un'eccezione e conservarne lo stack di chiamate per poterlo rigenerare in un secondo momento, usare la classe System.Runtime.ExceptionServices.ExceptionDispatchInfo. Questa classe fornisce i metodi e le proprietà seguenti (tra gli altri):
- Usare ExceptionDispatchInfo.Capture(Exception) per acquisire un'eccezione e uno stack di chiamate.
- Usare ExceptionDispatchInfo.Throw() per ripristinare lo stato salvato quando l'eccezione è stata acquisita e rigenerare l'eccezione acquisita.
- 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
Vedi anche
Commenti e suggerimenti
https://aka.ms/ContentUserFeedback.
Presto disponibile: Nel corso del 2024 verranno gradualmente disattivati i problemi di GitHub come meccanismo di feedback per il contenuto e ciò verrà sostituito con un nuovo sistema di feedback. Per altre informazioni, vedereInvia e visualizza il feedback per