C# Language
Undantagshantering
Sök…
Grundläggande undantagshantering
try
{
/* code that could throw an exception */
}
catch (Exception ex)
{
/* handle the exception */
}
Observera att hantering av alla undantag med samma kod ofta inte är den bästa metoden.
Detta används vanligtvis när några inre undantagshanteringsrutiner misslyckas, som en sista utväg.
Hantering av specifika undantagstyper
try
{
/* code to open a file */
}
catch (System.IO.FileNotFoundException)
{
/* code to handle the file being not found */
}
catch (System.IO.UnauthorizedAccessException)
{
/* code to handle not being allowed access to the file */
}
catch (System.IO.IOException)
{
/* code to handle IOException or it's descendant other than the previous two */
}
catch (System.Exception)
{
/* code to handle other errors */
}
Var försiktig så att undantag utvärderas i ordning och arv tillämpas. Så du måste börja med de mest specifika och sluta med sin förfader. Vid varje given punkt kommer endast ett fångstblock att köras.
Använda undantagsobjektet
Du får skapa och kasta undantag i din egen kod. Att instansera ett undantag görs på samma sätt som alla andra C # -objekt.
Exception ex = new Exception();
// constructor with an overload that takes a message string
Exception ex = new Exception("Error message");
Du kan sedan använda throw
nyckelordet för att höja undantaget:
try
{
throw new Exception("Error");
}
catch (Exception ex)
{
Console.Write(ex.Message); // Logs 'Error' to the output window
}
Obs! Om du kastar ett nytt undantag i ett fångstblock, se till att det ursprungliga undantaget skickas som "inre undantag", t.ex.
void DoSomething()
{
int b=1; int c=5;
try
{
var a = 1;
b = a - 1;
c = a / b;
a = a / c;
}
catch (DivideByZeroException dEx) when (b==0)
{
// we're throwing the same kind of exception
throw new DivideByZeroException("Cannot divide by b because it is zero", dEx);
}
catch (DivideByZeroException dEx) when (c==0)
{
// we're throwing the same kind of exception
throw new DivideByZeroException("Cannot divide by c because it is zero", dEx);
}
}
void Main()
{
try
{
DoSomething();
}
catch (Exception ex)
{
// Logs full error information (incl. inner exception)
Console.Write(ex.ToString());
}
}
I det här fallet antas att undantaget inte kan hanteras men viss användbar information läggs till meddelandet (och det ursprungliga undantaget kan fortfarande nås via ex.InnerException
genom ett yttre undantagsblock).
Det kommer att visa något som:
System.DivideByZeroException: Kan inte dela med b eftersom det är noll ---> System.DivideByZeroException: Försökt dela med noll.
på UserQuery.g__DoSomething0_0 () i C: [...] \ LINQPadQuery.cs: line 36
--- Slut på inre undantags stackspår ---
på UserQuery.g__DoSomething0_0 () i C: [...] \ LINQPadQuery.cs: line 42
på UserQuery.Main () i C: [...] \ LINQPadQuery.cs: line 55
Om du försöker det här exemplet i LinqPad kommer du att märka att radnumren inte är mycket meningsfulla (de hjälper dig inte alltid). Men att skicka en användbar feltext som föreslagits ovan ofta minskar avsevärt tiden för att spåra felets placering, vilket i detta exempel är tydligt linjen
c = a / b;
i funktionen DoSomething()
.
Slutligen blockera
try
{
/* code that could throw an exception */
}
catch (Exception)
{
/* handle the exception */
}
finally
{
/* Code that will be executed, regardless if an exception was thrown / caught or not */
}
try / catch / finally
kan vara mycket praktiskt när du läser från filer.
Till exempel:
FileStream f = null;
try
{
f = File.OpenRead("file.txt");
/* process the file here */
}
finally
{
f?.Close(); // f may be null, so use the null conditional operator.
}
Ett försöksblock måste följas av antingen en catch
eller ett finally
block. Eftersom det inte finns något fångstblock kommer verkställandet att orsaka uppsägning. Innan avslutningen kommer uttalanden inuti det slutliga blocket att köras.
I filläsningen kunde vi ha använt ett using
eftersom FileStream
(vad OpenRead
returnerar) implementerar IDisposable
.
Även om det finns en return
uttalande try
blocket, finally
kommer att blockera brukar köra; det finns några fall där det inte kommer att:
- När ett StackOverflow inträffar .
-
Environment.FailFast
- Ansökningsprocessen avlivas, vanligtvis av en extern källa.
Implementering av IErrorHandler för WCF Services
Implementering av IErrorHandler för WCF-tjänster är ett utmärkt sätt att centralisera felhantering och loggning. Den implementering som visas här bör fånga alla obehandlade undantag som kastas till följd av ett samtal till en av dina WCF-tjänster. I detta exempel visas också hur man returnerar ett anpassat objekt och hur man returnerar JSON snarare än standard XML.
Implementera IErrorHandler:
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
using System.Runtime.Serialization.Json;
using System.ServiceModel;
using System.ServiceModel.Web;
namespace BehaviorsAndInspectors
{
public class ErrorHandler : IErrorHandler
{
public bool HandleError(Exception ex)
{
// Log exceptions here
return true;
} // end
public void ProvideFault(Exception ex, MessageVersion version, ref Message fault)
{
// Get the outgoing response portion of the current context
var response = WebOperationContext.Current.OutgoingResponse;
// Set the default http status code
response.StatusCode = HttpStatusCode.InternalServerError;
// Add ContentType header that specifies we are using JSON
response.ContentType = new MediaTypeHeaderValue("application/json").ToString();
// Create the fault message that is returned (note the ref parameter) with BaseDataResponseContract
fault = Message.CreateMessage(
version,
string.Empty,
new CustomReturnType { ErrorMessage = "An unhandled exception occurred!" },
new DataContractJsonSerializer(typeof(BaseDataResponseContract), new List<Type> { typeof(BaseDataResponseContract) }));
if (ex.GetType() == typeof(VariousExceptionTypes))
{
// You might want to catch different types of exceptions here and process them differently
}
// Tell WCF to use JSON encoding rather than default XML
var webBodyFormatMessageProperty = new WebBodyFormatMessageProperty(WebContentFormat.Json);
fault.Properties.Add(WebBodyFormatMessageProperty.Name, webBodyFormatMessageProperty);
} // end
} // end class
} // end namespace
I det här exemplet kopplar vi hanteraren till servicebeteendet. Du kan också koppla detta till IEndpointBehavior, IContractBeh Fräör eller IOperationBeh Fräsen på liknande sätt.
Bifoga till servicebeteenden:
using System;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
namespace BehaviorsAndInspectors
{
public class ErrorHandlerExtension : BehaviorExtensionElement, IServiceBehavior
{
public override Type BehaviorType
{
get { return GetType(); }
}
protected override object CreateBehavior()
{
return this;
}
private IErrorHandler GetInstance()
{
return new ErrorHandler();
}
void IServiceBehavior.AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) { } // end
void IServiceBehavior.ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
var errorHandlerInstance = GetInstance();
foreach (ChannelDispatcher dispatcher in serviceHostBase.ChannelDispatchers)
{
dispatcher.ErrorHandlers.Add(errorHandlerInstance);
}
}
void IServiceBehavior.Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } // end
} // end class
} // end namespace
Configs i Web.config:
...
<system.serviceModel>
<services>
<service name="WebServices.MyService">
<endpoint binding="webHttpBinding" contract="WebServices.IMyService" />
</service>
</services>
<extensions>
<behaviorExtensions>
<!-- This extension if for the WCF Error Handling-->
<add name="ErrorHandlerBehavior" type="WebServices.BehaviorsAndInspectors.ErrorHandlerExtensionBehavior, WebServices, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
</behaviorExtensions>
</extensions>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="true"/>
<ErrorHandlerBehavior />
</behavior>
</serviceBehaviors>
</behaviors>
....
</system.serviceModel>
...
Här är några länkar som kan vara till hjälp i det här ämnet:
https://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.ierrorhandler(v=vs.100).aspx
Andra exempel:
IErrorHandler returnerar fel meddelandekropp när HTTP-statuskod är 401 obehörig
IErrorHandler verkar inte hantera mina fel i WCF .. några idéer?
Hur gör jag anpassad WCF-felhanterare som returnerar JSON-svar med http-kod som inte är OK?
Hur ställer du in rubriken Content-Type för en HttpClient-begäran?
Skapa anpassade undantag
Du får implementera anpassade undantag som kan kastas precis som alla andra undantag. Detta är meningsfullt när du vill göra dina undantag åtskilda från andra fel under körning.
I det här exemplet kommer vi att skapa ett anpassat undantag för tydlig hantering av problem som applikationen kan ha när du analyserar en komplex ingång.
Skapa anpassad undantagsklass
För att skapa ett anpassat undantag skapar du en underklass av Exception
:
public class ParserException : Exception
{
public ParserException() :
base("The parsing went wrong and we have no additional information.") { }
}
Anpassat undantag blir mycket användbart när du vill ge ytterligare information till catcher:
public class ParserException : Exception
{
public ParserException(string fileName, int lineNumber) :
base($"Parser error in {fileName}:{lineNumber}")
{
FileName = fileName;
LineNumber = lineNumber;
}
public string FileName {get; private set;}
public int LineNumber {get; private set;}
}
När du nu catch(ParserException x)
kommer du att ha ytterligare semantik för att finjustera undantagshantering.
Anpassade klasser kan implementera följande funktioner för att stödja ytterligare scenarier.
återkastning
Under analysen är det ursprungliga undantaget fortfarande av intresse. I det här exemplet är det en FormatException
eftersom koden försöker analysera en strängbit, som förväntas vara ett nummer. I detta fall bör det anpassade undantaget stödja inkluderingen av ' InnerException ':
//new constructor:
ParserException(string msg, Exception inner) : base(msg, inner) {
}
serialisering
I vissa fall kan dina undantag behöva korsa AppDomain-gränserna. Detta är fallet om din parser körs i sin egen AppDomain för att stödja het omladdning av nya parserkonfigurationer. I Visual Studio kan du använda Exception
mall för att generera kod som denna.
[Serializable]
public class ParserException : Exception
{
// Constructor without arguments allows throwing your exception without
// providing any information, including error message. Should be included
// if your exception is meaningful without any additional details. Should
// set message by calling base constructor (default message is not helpful).
public ParserException()
: base("Parser failure.")
{}
// Constructor with message argument allows overriding default error message.
// Should be included if users can provide more helpful messages than
// generic automatically generated messages.
public ParserException(string message)
: base(message)
{}
// Constructor for serialization support. If your exception contains custom
// properties, read their values here.
protected ParserException(SerializationInfo info, StreamingContext context)
: base(info, context)
{}
}
Använda ParserException
try
{
Process.StartRun(fileName)
}
catch (ParserException ex)
{
Console.WriteLine($"{ex.Message} in ${ex.FileName}:${ex.LineNumber}");
}
catch (PostProcessException x)
{
...
}
Du kan också använda anpassade undantag för att fånga och förpacka undantag. På så sätt kan många olika fel konverteras till en enda feltyp som är mer användbar för applikationen:
try
{
int foo = int.Parse(token);
}
catch (FormatException ex)
{
//Assuming you added this constructor
throw new ParserException(
$"Failed to read {token} as number.",
FileName,
LineNumber,
ex);
}
När du hanterar undantag genom att höja dina egna anpassade undantag bör du i allmänhet inkludera en referens till det ursprungliga undantaget i egenskapen InnerException
, som visas ovan.
Säkerhetsproblem
Om att avslöja orsaken till undantaget kan äventyra säkerheten genom att låta användare se de inre funktionerna i din applikation kan det vara en dålig idé att lägga in det inre undantaget. Detta kan gälla om du skapar ett klassbibliotek som kommer att användas av andra.
Så här kan du höja ett anpassat undantag utan att lägga in det inre undantaget:
try
{
// ...
}
catch (SomeStandardException ex)
{
// ...
throw new MyCustomException(someMessage);
}
Slutsats
När du höjer ett anpassat undantag (antingen med omslag eller med ett oöppnat nytt undantag) bör du ta upp ett undantag som är meningsfullt för den som ringer. Till exempel kanske en användare av ett klassbibliotek inte vet mycket om hur det biblioteket utför sitt interna arbete. Undantagen som kastas av klassbibliotekets beroenden är inte meningsfulla. Snarare vill användaren ha ett undantag som är relevant för hur klassbiblioteket använder dessa beroenden på ett felaktigt sätt.
try
{
// ...
}
catch (IOException ex)
{
// ...
throw new StorageServiceException(@"The Storage Service encountered a problem saving
your data. Please consult the inner exception for technical details.
If you are not able to resolve the problem, please call 555-555-1234 for technical
assistance.", ex);
}
Undantag Anti-mönster
Svälja undantag
Man bör alltid kasta undantag på följande sätt:
try
{
...
}
catch (Exception ex)
{
...
throw;
}
Om du kastar ett undantag, som nedan, kommer du att dölja det ursprungliga undantaget och förlorar det ursprungliga stackspåret. Man ska aldrig göra detta! Stapelspåret före fångst och återföring kommer att gå förlorat.
try
{
...
}
catch (Exception ex)
{
...
throw ex;
}
Hantering av baseballundantag
Man bör inte använda undantag som ersättning för normala flödeskontrollkonstruktioner som ifall-då-uttalanden och medan slingor. Detta antimönster kallas ibland Baseball Exception Handling .
Här är ett exempel på antimönstret:
try
{
while (AccountManager.HasMoreAccounts())
{
account = AccountManager.GetNextAccount();
if (account.Name == userName)
{
//We found it
throw new AccountFoundException(account);
}
}
}
catch (AccountFoundException found)
{
Console.Write("Here are your account details: " + found.Account.Details.ToString());
}
Här är ett bättre sätt att göra det:
Account found = null;
while (AccountManager.HasMoreAccounts() && (found==null))
{
account = AccountManager.GetNextAccount();
if (account.Name == userName)
{
//We found it
found = account;
}
}
Console.Write("Here are your account details: " + found.Details.ToString());
fångst (undantag)
Det finns nästan inga (vissa säger ingen!) Skäl att fånga den generiska undantagstypen i din kod. Du bör bara fånga de undantagstyper du förväntar dig att hända eftersom du döljer fel i din kod annars.
try
{
var f = File.Open(myfile);
// do something
}
catch (Exception x)
{
// Assume file not found
Console.Write("Could not open file");
// but maybe the error was a NullReferenceException because of a bug in the file handling code?
}
Bättre göra:
try
{
var f = File.Open(myfile);
// do something which should normally not throw exceptions
}
catch (IOException)
{
Console.Write("File not found");
}
// Unfortunatelly, this one does not derive from the above, so declare separatelly
catch (UnauthorizedAccessException)
{
Console.Write("Insufficient rights");
}
Om något annat undantag inträffar låter vi med avsikt applikationen krascha, så det går direkt i felsökaren så att vi kan lösa problemet. Vi får inte skicka ett program där några andra undantag än dessa händer ändå, så det är inte ett problem att ha en krasch.
Följande är också ett dåligt exempel eftersom det använder undantag för att lösa ett programmeringsfel. Det är inte det de är designade för.
public void DoSomething(String s)
{
if (s == null)
throw new ArgumentNullException(nameof(s));
// Implementation goes here
}
try
{
DoSomething(myString);
}
catch(ArgumentNullException x)
{
// if this happens, we have a programming error and we should check
// why myString was null in the first place.
}
Samla undantag / flera undantag från en metod
Vem säger att du inte kan kasta flera undantag på en metod. Om du inte är van vid att leka med AggregateExceptions kan du frestas att skapa din egen datastruktur för att representera många saker som går fel. Det finns naturligtvis en annan datastruktur som inte är ett undantag som skulle vara mer idealisk såsom resultaten av en validering. Även om du spelar med AggregateExceptions kan du vara på den mottagande sidan och alltid hantera dem och inte inser att de kan vara till nytta för dig.
Det är ganska troligt att en metod körs och även om det kommer att vara ett misslyckande som helhet vill du lyfta fram flera saker som gick fel i de undantag som kastas. Som exempel kan detta beteende ses med hur parallella metoder fungerade var en uppgift uppdelad i flera trådar och valfritt antal av dem kunde kasta undantag och detta måste rapporteras. Här är ett dumt exempel på hur du kan dra nytta av detta:
public void Run()
{
try
{
this.SillyMethod(1, 2);
}
catch (AggregateException ex)
{
Console.WriteLine(ex.Message);
foreach (Exception innerException in ex.InnerExceptions)
{
Console.WriteLine(innerException.Message);
}
}
}
private void SillyMethod(int input1, int input2)
{
var exceptions = new List<Exception>();
if (input1 == 1)
{
exceptions.Add(new ArgumentException("I do not like ones"));
}
if (input2 == 2)
{
exceptions.Add(new ArgumentException("I do not like twos"));
}
if (exceptions.Any())
{
throw new AggregateException("Funny stuff happended during execution", exceptions);
}
}
Häckning av undantag och prova fångstblock.
Man kan häcka ett undantag / try
catch
inuti det andra.
På så sätt kan man hantera små kodblock som kan fungera utan att störa hela mekanismen.
try
{
//some code here
try
{
//some thing which throws an exception. For Eg : divide by 0
}
catch (DivideByZeroException dzEx)
{
//handle here only this exception
//throw from here will be passed on to the parent catch block
}
finally
{
//any thing to do after it is done.
}
//resume from here & proceed as normal;
}
catch(Exception e)
{
//handle here
}
Obs: Undvik att svälja undantag när du kastar till föräldrarens fångstblock
Bästa metoder
Fusklapp
DO | DO NOT |
---|---|
Kontrollflöde med kontrollförklaringar | Kontrollera flöde med undantag |
Håll koll på ignorerat (absorberat) undantag genom att logga in | Ignorera undantag |
Upprepa undantaget med throw | Kasta undantag - throw new ArgumentNullException() eller throw ex |
Kasta fördefinierade systemundantag | Kasta anpassade undantag som liknar fördefinierade systemundantag |
Kasta anpassat / fördefinierat undantag om det är avgörande för applikationslogiken | Kasta anpassade / fördefinierade undantag för att ange en varning i flödet |
Fånga undantag som du vill hantera | Fånga alla undantag |
Hantera INTE affärslogik med undantag.
Flödeskontroll ska INTE göras med undantag. Använd villkorliga uttalanden istället. Om en kontroll kan göras med if-else
uttalanden tydligt, använd inte undantag eftersom det minskar läsbarheten och prestandan.
Tänk på följande utdrag av Mr. Bad Practices:
// This is a snippet example for DO NOT
object myObject;
void DoingSomethingWithMyObject()
{
Console.WriteLine(myObject.ToString());
}
När körningen når Console.WriteLine(myObject.ToString());
ansökan kommer att kasta en NullReferenceException. Mr. Bad Practices insåg att myObject
är noll och redigerade sin kod för att fånga & hantera NullReferenceException
:
// This is a snippet example for DO NOT
object myObject;
void DoingSomethingWithMyObject()
{
try
{
Console.WriteLine(myObject.ToString());
}
catch(NullReferenceException ex)
{
// Hmmm, if I create a new instance of object and assign it to myObject:
myObject = new object();
// Nice, now I can continue to work with myObject
DoSomethingElseWithMyObject();
}
}
Eftersom föregående kodavsnitt bara täcker undantagslogik, vad ska jag göra om myObject
inte är noll vid denna punkt? Var ska jag täcka denna del av logiken? Rätt efter Console.WriteLine(myObject.ToString());
? Vad sägs om efter try...catch
?
Vad sägs om Mr. Best Practices? Hur skulle han hantera det här?
// This is a snippet example for DO
object myObject;
void DoingSomethingWithMyObject()
{
if(myObject == null)
myObject = new object();
// When execution reaches this point, we are sure that myObject is not null
DoSomethingElseWithMyObject();
}
Mr. Best Practices uppnådde samma logik med färre kod och en tydlig och förståelig logik.
Kasta INTE undantag
Att kasta undantag är dyrt. Det påverkar resultatet negativt. För kod som rutinmässigt misslyckas kan du använda designmönster för att minimera prestandaproblem. Detta ämne beskriver två designmönster som är användbara när undantag kan påverka prestandan betydligt.
Absorbera INTE undantag utan avverkning
try
{
//Some code that might throw an exception
}
catch(Exception ex)
{
//empty catch block, bad practice
}
Svälja aldrig undantag. Att ignorera undantag kommer att spara det ögonblicket men skapar ett kaos för underhåll senare. När du loggar undantag bör du alltid logga undantagsinstansen så att hela stapelspåret loggas och inte undantagsmeddelandet.
try
{
//Some code that might throw an exception
}
catch(NullException ex)
{
LogManager.Log(ex.ToString());
}
Ta inte några undantag som du inte kan hantera
Många resurser, som den här , uppmanar dig starkt att överväga varför du fångar ett undantag på den plats där du fångar det. Du bör bara få ett undantag om du kan hantera det på den platsen. Om du kan göra något där för att hjälpa till att mildra problemet, till exempel att prova en alternativ algoritm, ansluta till en säkerhetskopierad databas, prova ett annat filnamn, vänta 30 sekunder och försöka igen eller meddela en administratör, kan du fånga felet och göra det. Om det inte finns någonting som du kan på ett rimligt och rimligt sätt bara "låt det gå" och låt undantaget hanteras på en högre nivå. Om undantaget är tillräckligt katastrofalt och det inte finns något annat rimligt alternativ än att hela programmet kraschar på grund av svårighetsgraden av problemet, låt det krascha.
try
{
//Try to save the data to the main database.
}
catch(SqlException ex)
{
//Try to save the data to the alternative database.
}
//If anything other than a SqlException is thrown, there is nothing we can do here. Let the exception bubble up to a level where it can be handled.
Obehandlat och trådundantag
AppDomain.UnhandledException Den här händelsen tillhandahåller anmälan om oupptagna undantag.Det tillåter applikationen att logga information om undantaget innan systemets standardhanterare rapporterar undantaget till användaren och avslutar applikationen. Om tillräcklig information om applikationens tillstånd är tillgängligt, åtgärder kan vidtas - till exempel att spara programdata för senare återställning. Varning rekommenderas, eftersom programdata kan bli skadade när undantag inte hanteras.
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledException);
}
Application.ThreadException Denna händelse gör att din Windows Forms-applikation kan hantera på annat sätt obehandlade undantag som inträffar i Windows Forms-trådar. Anslut dina händelseshanterare till ThreadException-händelsen för att hantera dessa undantag, vilket kommer att lämna din ansökan i okänt tillstånd. Om möjligt ska undantag hanteras av ett strukturerat undantagshanteringsblock.
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledException);
Application.ThreadException += new ThreadExceptionEventHandler(ThreadException);
}
Och slutligen undantagshantering
static void UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = (Exception)e.ExceptionObject;
// your code
}
static void ThreadException(object sender, ThreadExceptionEventArgs e)
{
Exception ex = e.Exception;
// your code
}
Kasta ett undantag
Din kod kan, och ofta borde, kasta ett undantag när något ovanligt har hänt.
public void WalkInto(Destination destination)
{
if (destination.Name == "Mordor")
{
throw new InvalidOperationException("One does not simply walk into Mordor.");
}
// ... Implement your normal walking code here.
}