Anteckning
Åtkomst till den här sidan kräver auktorisering. Du kan prova att logga in eller ändra kataloger.
Åtkomst till den här sidan kräver auktorisering. Du kan prova att ändra kataloger.
Korrekt undantagshantering är viktigt för programmets tillförlitlighet. Du kan avsiktligt hantera förväntade undantag för att förhindra att appen kraschar. En kraschad app är dock mer tillförlitlig och kan diagnostiseras än en app med odefinierat beteende.
I den här artikeln beskrivs metodtips för att hantera och skapa undantag.
Hantera undantag
Följande metodtips gäller hur du hanterar undantag:
- Använd try/catch/finally-block för att återställa från fel eller frigöra resurser
- Hantera vanliga villkor för att undvika undantag
- Hantera annullering och asynkrona undantag
- designklasser så att undantag kan undvikas
- återställ tillståndet när metoderna inte slutförs på grund av undantag
- Fånga och vidarebefordra undantag korrekt
Använd try/catch/finally-block för att återställa från fel eller frigöra resurser
För kod som potentiellt kan generera ett undantag och när din app kan återställas från det undantaget använder du try
/catch
block runt koden. I catch
-block ordnar du alltid undantagen från de mest härledda till de minst härledda. (Alla undantag härleds från klassen Exception. Fler härledda undantag hanteras inte av en catch
-sats som föregås av en catch
-sats för en bas undantagsklass.) När koden inte kan återställas från ett undantag ska du inte fånga det undantaget. Aktivera metoder längre upp i anropsstacken för att återställa om möjligt.
Frigör resurser som allokeras med antingen using
-instruktioner eller finally
-block. Föredra using
instruktioner för att automatiskt rensa resurser när undantag utlöses. Använd finally
block för att rensa resurser som inte implementerar IDisposable. Kod i en finally
-sats körs nästan alltid även när undantag utlöses.
Hantera vanliga villkor för att undvika undantag
För villkor som sannolikt inträffar men som kan utlösa ett undantag kan du överväga att hantera dem på ett sätt som undviker undantaget. Om du till exempel försöker stänga en anslutning som redan är stängd får du en InvalidOperationException
. Du kan undvika det genom att använda en if
-instruktion för att kontrollera anslutningstillståndet innan du försöker stänga det.
if (conn.State != ConnectionState.Closed)
{
conn.Close();
}
If conn.State <> ConnectionState.Closed Then
conn.Close()
End IF
Om du inte kontrollerar anslutningstillståndet innan du stänger kan du se InvalidOperationException
undantag.
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
Vilken metod du ska välja beror på hur ofta du förväntar dig att händelsen ska inträffa.
Använd undantagshantering om händelsen inte inträffar ofta, dvs. om händelsen verkligen är exceptionell och indikerar ett fel, till exempel ett oväntat filslut. När du använder undantagshantering körs mindre kod under normala förhållanden.
Kontrollera om det finns feltillstånd i koden om händelsen inträffar rutinmässigt och kan betraktas som en del av normal exekvering. När du söker efter vanliga feltillstånd körs mindre kod eftersom du undviker undantag.
Notera
Kontroller i förväg eliminerar undantag för det mesta. Det kan dock finnas loppvillkor där det skyddade villkoret ändras mellan kontrollen och åtgärden, och i så fall kan du fortfarande ådra dig ett undantag.
Anropa Try*
metoder för att undvika undantag
Om prestandakostnaden för undantag är oöverkomlig tillhandahåller vissa .NET-biblioteksmetoder alternativa former av felhantering. Till exempel genererar Int32.Parse en OverflowException om värdet som ska parsas är för stort för att representeras av Int32. Men Int32.TryParse utlöser inte det här undantaget. I stället returnerar den ett booleskt värde och har en out
-parameter som innehåller det parsade giltiga heltalet när det lyckas.
Dictionary<TKey,TValue>.TryGetValue har liknande beteende för att försöka hämta ett värde från en ordlista.
Fånga annullering och asynkrona undantag
Det är bättre att fånga OperationCanceledException i stället för TaskCanceledException, som härleds från OperationCanceledException
, när du anropar en asynkron metod. Många asynkrona metoder utlöser ett OperationCanceledException undantag om annullering begärs. Dessa undantag gör att körningen effektivt kan stoppas och anropstacken återställs när en annulleringsbegäran har observerats.
Asynkrona metoder lagrar undantag som utlöses under exekveringen i den uppgift de returnerar. Om ett undantag lagras i den returnerade uppgiften, kommer undantaget att utlösas när man väntar på uppgiften. Användningsfel, till exempel ArgumentException, genereras fortfarande synkront. För mer information, se asynkrona undantag.
Utforma klasser så att undantag kan undvikas
En klass kan tillhandahålla metoder eller egenskaper som gör att du kan undvika att göra ett anrop som utlöser ett undantag. Klassen FileStream innehåller till exempel metoder som hjälper dig att avgöra om slutet av filen har nåtts. Du kan anropa dessa metoder för att undvika undantaget som utlöses om du läser förbi slutet av filen. I följande exempel visas hur du läser till slutet av en fil utan att utlösa ett undantag:
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
Ett annat sätt att undvika undantag är att returnera null
(eller standard) för de vanligaste felfallen i stället för att utlösa ett undantag. Ett vanligt felfall kan betraktas som ett normalt kontrollflöde. Genom att returnera null
(eller standard) i dessa fall minimerar du prestandapåverkan för en app.
För värdetyper bör du överväga om du vill använda Nullable<T>
eller default
som felindikator för din app. Med hjälp av Nullable<Guid>
blir default
null
i stället för Guid.Empty
. Ibland kan det vara tydligare att lägga till Nullable<T>
när ett värde finns eller saknas. Ibland kan tillägg av Nullable<T>
skapa extra ärenden som inte är nödvändiga och som bara kan skapa potentiella felkällor.
Återställ tillståndet när metoderna inte slutförs på grund av undantag
Anropare bör kunna anta att det inte finns några biverkningar när ett undantag genereras från en metod. Om du till exempel har kod som överför pengar genom att ta ut från ett konto och sätta in på ett annat konto, och ett undantag utlöses när insättningen utförs, vill du inte att uttaget ska gälla.
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
Föregående metod utlöser inga undantag direkt. Du måste dock skriva metoden så att uttagen återförs om insättningsåtgärden misslyckas.
Ett sätt att hantera den här situationen är att fånga upp eventuella undantag som utlöses av insättningstransaktionen och återställa uttagen.
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
Det här exemplet visar användningen av throw
för att kasta det ursprungliga undantaget igen, vilket gör det lättare för anropare att se den verkliga orsaken till problemet utan att behöva granska egenskapen InnerException. Ett alternativ är att utlösa ett nytt undantag och inkludera det ursprungliga undantaget som det inre undantaget.
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
Fånga in och återkasta undantag korrekt
När ett undantag har genererats är en del av den information som det bär stackspårningen. Stackspårningen är en lista över metodanropshierarkin som börjar med metoden som genererar undantaget och slutar med metoden som fångar undantaget. Om du återskapar ett undantag genom att ange undantaget i throw
-instruktionen, till exempel throw e
, startas stackspårningen om med den aktuella metoden och listan över metodanrop mellan den ursprungliga metoden som utlöste undantaget och den aktuella metoden går förlorad. För att behålla den ursprungliga stackspårningsinformationen med undantaget finns det två alternativ som är beroende av var du återväxtar undantaget från:
- Om du återaktiverar undantaget från hanteraren (
catch
block) som har fångat undantagsinstansen använder duthrow
-instruktionen utan att ange undantaget. Kodanalysregeln CA2200 hjälper dig att hitta platser i koden där du oavsiktligt kan förlora stackspårningsinformation. - Om du återkastar undantaget från en annan plats än undantagshanteraren (
catch
block), använder du ExceptionDispatchInfo.Capture(Exception) för att fånga undantaget i hanteraren och ExceptionDispatchInfo.Throw() när du vill återkasta det. Du kan använda egenskapen ExceptionDispatchInfo.SourceException för att inspektera det fångade undantaget.
I följande exempel visas hur klassen ExceptionDispatchInfo kan användas och hur utdata kan se ut.
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();
Om filen i exempelkoden inte finns skapas följande utdata:
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
Utlösa undantag
Följande metodtips gäller hur du utlöser undantag:
- Använd fördefinierade undantagstyper
- Använd undantagsbyggarmetoder
- Inkludera ett lokaliserat strängmeddelande
- Använd korrekt grammatik
- Placera throw-uttryck ordentligt
- Generera inte undantag i finally-satser
- Generera inte undantag från oväntade platser
- Kasta undantag för argumentverifiering synkront
Använda fördefinierade undantagstyper
Introducera endast en ny undantagsklass när en fördefinierad klass inte gäller. Till exempel:
- Om en egenskapsuppsättning eller ett metodanrop inte är lämpligt med tanke på objektets aktuella tillstånd utlöser du ett InvalidOperationException undantag.
- Om ogiltiga parametrar skickas utlöser du ett ArgumentException undantag eller någon av de fördefinierade klasserna som härleds från ArgumentException.
Notera
Även om det är bäst att använda fördefinierade undantagstyper när det är möjligt bör du inte skapa några reserverade undantagstyper, till exempel AccessViolationException, IndexOutOfRangeException, NullReferenceException och StackOverflowException. Mer information finns i CA2201: Skapa inte reserverade undantagstyper.
Använd metoder för undantagsbyggare
Det är vanligt att en klass genererar samma undantag från olika platser i implementeringen. Undvik överdriven kod genom att skapa en hjälpmetod som skapar undantaget och returnerar det. Till exempel:
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
Vissa viktiga .NET-undantagstyper har sådana statiska throw
hjälpmetoder som allokerar och genererar undantaget. Du bör anropa dessa metoder i stället för att skapa och utlösa motsvarande undantagstyp:
- 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
Tips
Följande kodanalysregler kan hjälpa dig att hitta platser i koden där du kan dra nytta av de här statiska throw
hjälparna: CA1510, CA1511, CA1512och CA1513.
Om du implementerar en asynkron metod anropar du CancellationToken.ThrowIfCancellationRequested() i stället för att kontrollera om annullering begärdes och sedan konstruerar och genererar OperationCanceledException. Mer information finns i CA2250.
Inkludera ett lokaliserat strängmeddelande
Felmeddelandet som användaren ser härleds från egenskapen Exception.Message för undantaget som utlöstes och inte från namnet på undantagsklassen. Vanligtvis tilldelar du ett värde till egenskapen Exception.Message genom att skicka meddelandesträngen till argumentet message
för en undantagskonstruktor.
För lokaliserade program bör du ange en lokaliserad meddelandesträng för varje undantag som programmet kan utlösa. Du använder resursfiler för att ange lokaliserade felmeddelanden. Information om hur du lokaliserar program och hämtar lokaliserade strängar finns i följande artiklar:
- Så här skapar du användardefinierade undantag med lokaliserade undantagsmeddelanden
- Resurser i .NET-appar
- System.Resources.ResourceManager
Använd korrekt grammatik
Skriv tydliga meningar och inkludera avslutande skiljetecken. Varje mening i strängen som tilldelats egenskapen Exception.Message ska sluta i en punkt. Till exempel använder "Loggtabellen har spillts över" rätt grammatik och skiljetecken.
Placera utkastsuttryck väl
Placera throw-instruktioner där stacktracen är till hjälp. Stackspårningen börjar vid -instruktionen där undantaget genereras och slutar vid catch
-instruktionen som fångar undantaget.
Skapa inte undantag i slutsatser
Skapa inte undantag i finally
-satser. Mer information finns i kodanalysregeln CA2219.
Skapa inte undantag från oväntade platser
Vissa metoder, till exempel Equals
, GetHashCode
och ToString
metoder, statiska konstruktorer och likhetsoperatorer, bör inte utlösa undantag. Mer information finns i kodanalysregeln CA1065.
Generera argumentverifieringsfel synkront
I uppgiftsreturmetoder bör du verifiera argument och utlösa eventuella motsvarande undantag, till exempel ArgumentException och ArgumentNullException, innan du anger den asynkrona delen av metoden. Undantag som genereras i den asynkrona delen av metoden lagras i den returnerade aktiviteten och visas inte förrän uppgiften till exempel väntar. Mer information finns i Undantag i uppgiftsreturmetoder.
Anpassade undantagstyper
Följande metodtips gäller anpassade undantagstyper:
-
Avsluta undantagsklassnamn med
Exception
- Inkludera tre konstruktorer
- Ange ytterligare egenskaper efter behov
Avsluta undantagsklassnamn med Exception
När ett anpassat undantag krävs namnger du det på rätt sätt och härleder det från klassen Exception. Till exempel:
public class MyFileNotFoundException : Exception
{
}
Public Class MyFileNotFoundException
Inherits Exception
End Class
Inkludera tre konstruktorer
Använd minst tre vanliga konstruktorer när du skapar dina egna undantagsklasser: den parameterlösa konstruktorn, en konstruktor som tar ett strängmeddelande och en konstruktor som tar ett strängmeddelande och ett inre undantag.
- Exception(), som använder standardvärden.
- Exception(String), som accepterar ett strängmeddelande.
- Exception(String, Exception), som accepterar ett strängmeddelande och ett inre undantag.
Ett exempel finns i Så här skapar du användardefinierade undantag.
Ange ytterligare egenskaper efter behov
Ange ytterligare egenskaper för ett undantag (utöver den anpassade meddelandesträngen) endast när det finns ett programmatiskt scenario där ytterligare information är användbar. Till exempel tillhandahåller FileNotFoundException egenskapen FileName.