Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Kivételkezelési utasítások –
A kivételekkel és throw az try utasításokkal dolgozhat. Az utasításthrow kivételt jelezhet. Az utasítással try elfoghatja és kezelheti a kódblokk végrehajtása során esetlegesen előforduló kivételeket.
Az throw utasítás
Az throw utasítás kivételt jelez:
if (shapeAmount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(shapeAmount), "Amount of shapes must be positive.");
}
throw e; Egy utasításban a kifejezés e eredményének implicit módon konvertálhatónak System.Exceptionkell lennie.
Használhatja például a beépített kivételosztályokat vagy ArgumentOutOfRangeExceptionInvalidOperationExceptiona . A .NET a következő segédmetenek is biztosítja, hogy bizonyos feltételek mellett kivételeket adjon ki: ArgumentNullException.ThrowIfNull és ArgumentException.ThrowIfNullOrEmpty. Saját kivételosztályokat is meghatározhat, amelyekből származik System.Exception. További információ: Kivételek létrehozása és kivetése.
catch Egy blokkon belül egy throw; utasítással újra elvetheti a blokk által kezelt kivételtcatch:
try
{
ProcessShapes(shapeAmount);
}
catch (Exception e)
{
LogError(e, "Shape processing failed.");
throw;
}
Feljegyzés
throw; Megőrzi a kivétel eredeti veremnyomát, amely a Exception.StackTrace tulajdonságban van tárolva. Ezzel ellentétben frissíti throw e; a StackTrace tulajdonságot e.
Kivétel esetén a közös nyelvi futtatókörnyezet (CLR) megkeresi azt a blokkotcatch kivételt. Ha a jelenleg végrehajtott metódus nem tartalmaz ilyen blokkot catch , a CLR megvizsgálja az aktuális metódust nevesítő metódust, és így tovább a hívási vermet. Ha nem catch található blokk, a CLR leállítja a végrehajtó szálat. További információkért tekintse meg a C#-nyelv specifikációjának a kivételek kezelésévelfoglalkozó szakaszát.
A throw kifejezés
Kifejezésként is használható throw . Ez számos esetben kényelmes lehet, például:
a feltételes operátor. Az alábbi példa egy
throwkifejezést használ, amikor ArgumentException az átadott tömbargsüres:string first = args.Length >= 1 ? args[0] : throw new ArgumentException("Please supply at least one argument.");a null-szenesítő operátor. Az alábbi példa egy
throwkifejezés használatával ad vissza egy ArgumentNullException értéket, amikor a tulajdonsághoz rendelendő sztring a következőnull:public string Name { get => name; set => name = value ?? throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null"); }kifejezéssel testesített lambda vagy metódus. Az alábbi példa egy
throwkifejezéssel InvalidCastException jelzi, hogy az értékké alakítás DateTime nem támogatott:DateTime ToDateTime(IFormatProvider provider) => throw new InvalidCastException("Conversion to a DateTime is not supported.");
Az try utasítás
Az utasítást a try következő űrlapok bármelyikében használhatja: try-catch - a kód blokkon belüli try végrehajtása során esetlegesen előforduló kivételek kezelésére, try-finally - a blokk elhagyásakor try végrehajtott kód megadására, és try-catch-finally - az előző két űrlap kombinációjaként.
Az try-catch utasítás
Az utasítással kezelheti a try-catch kódblokk végrehajtása során esetlegesen előforduló kivételeket. Helyezze el a kódot, ahol kivétel léphet fel egy blokkon try belül.
A fogási záradék használatával adja meg a megfelelő catch blokkban kezelni kívánt kivételek alaptípusát:
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
Több fogási záradékot is megadhat:
try
{
var result = await ProcessAsync(-3, 4, cancellationToken);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
catch (OperationCanceledException)
{
Console.WriteLine("Processing is cancelled.");
}
Kivétel esetén a fogási záradékokat a rendszer a megadott sorrendben vizsgálja felülről lefelé. Legfeljebb csak egy catch blokk hajtható végre minden kidobott kivétel esetén. Ahogy az előző példa is mutatja, kihagyhatja egy kivételváltozó deklarációját, és csak a kivételtípust adhatja meg egy fogási záradékban. A megadott kivételtípus nélküli fogási záradék minden kivételnek megfelel, és ha van ilyen, akkor az utolsó fogási záradéknak kell lennie.
Ha ismét ki szeretne dobni egy kivételt, használja az throw utasítást, ahogy az alábbi példa is mutatja:
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e)
{
LogError(e, "Processing failed.");
throw;
}
Feljegyzés
throw; Megőrzi a kivétel eredeti veremnyomát, amely a Exception.StackTrace tulajdonságban van tárolva. Ezzel ellentétben frissíti throw e; a StackTrace tulajdonságot e.
Kivételszűrő when
A kivételtípus mellett megadhat egy kivételszűrőt is, amely tovább vizsgálja a kivételt, és eldönti, hogy a megfelelő catch blokk kezeli-e ezt a kivételt. A kivételszűrő egy logikai kifejezés, amely a when kulcsszót követi, ahogy az alábbi példa is mutatja:
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e) when (e is ArgumentException || e is DivideByZeroException)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
Az előző példa egy kivételszűrővel egyetlen blokkot catch biztosít két megadott típus kivételeinek kezeléséhez.
Több záradékot is megadhat catch ugyanahhoz a kivételtípushoz, ha kivételszűrőkkel különböztetik meg őket. Előfordulhat, hogy az egyik záradék kivételszűrővel nem rendelkezik. Ha létezik ilyen záradék, akkor a kivételtípust meghatározó záradékok közül az utolsónak kell lennie.
Ha egy catch záradék kivételszűrővel rendelkezik, megadhatja azt a kivételtípust, amely megegyezik a utána megjelenő záradék kivételtípusával catch vagy kevésbé származtatott típusával. Ha például egy kivételszűrő van jelen, catch (Exception e) a záradéknak nem kell az utolsó záradéknak lennie.
Kivételszűrők és hagyományos kivételkezelés
A kivételszűrők jelentős előnyöket biztosítanak a hagyományos kivételkezelési megközelítéssel szemben. A fő különbség a kivételkezelési logika kiértékelése:
-
Kivételszűrők (
when): A rendszer a verem feloldása előtt kiértékeli a szűrőkifejezést. Ez azt jelenti, hogy az eredeti hívásverem és az összes helyi változó érintetlen marad a szűrő kiértékelése során. -
Hagyományos
catchblokkok: A fogási blokk a verem feloldása után fut, ami értékes hibakeresési információk elvesztését eredményezheti.
Íme egy összehasonlítás, amely a különbséget mutatja:
public static void DemonstrateStackUnwindingDifference()
{
var localVariable = "Important debugging info";
try
{
ProcessWithExceptionFilter(localVariable);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("filter"))
{
// Exception filter: Stack not unwound yet.
// localVariable is still accessible in debugger.
// Call stack shows original throwing location.
Console.WriteLine($"Caught with filter: {ex.Message}");
Console.WriteLine($"Local variable accessible: {localVariable}");
}
try
{
ProcessWithTraditionalCatch(localVariable);
}
catch (InvalidOperationException ex)
{
// Traditional catch: Stack already unwound.
// Some debugging information may be lost.
if (ex.Message.Contains("traditional"))
{
Console.WriteLine($"Caught with if: {ex.Message}");
Console.WriteLine($"Local variable accessible: {localVariable}");
}
else
{
throw; // Re-throws and further modifies stack trace.
}
}
}
private static void ProcessWithExceptionFilter(string context)
{
throw new InvalidOperationException($"Exception for filter demo: {context}");
}
private static void ProcessWithTraditionalCatch(string context)
{
throw new InvalidOperationException($"Exception for traditional demo: {context}");
}
A kivételszűrők előnyei
- Jobb hibakeresési élmény: Mivel a verem nem oldható fel, amíg egy szűrő nem egyezik, a hibakeresők az eredeti hibapontot az összes helyi változó érintetlen állapotában jeleníthetik meg.
- Teljesítménybeli előnyök: Ha nem egyezik a szűrő, a kivétel továbbra is propagálást folytat a verem feloldása és helyreállítása nélkül.
- Tisztább kód: Több szűrő is képes kezelni az azonos kivételtípusú különböző feltételeket anélkül, hogy beágyazott if-else utasításokat kellene megadnia.
- Naplózás és diagnosztika: Megvizsgálhatja és naplózhatja a kivétel részleteit, mielőtt eldöntené, hogy kezeli-e a kivételt:
public static void DemonstrateDebuggingAdvantage()
{
var contextData = new Dictionary<string, object>
{
["RequestId"] = Guid.NewGuid(),
["UserId"] = "user123",
["Timestamp"] = DateTime.Now
};
try
{
// Simulate a deep call stack.
Level1Method(contextData);
}
catch (Exception ex) when (LogAndFilter(ex, contextData))
{
// This catch block may never execute if LogAndFilter returns false.
// But LogAndFilter can examine the exception while the stack is intact.
Console.WriteLine("Exception handled after logging");
}
}
private static void Level1Method(Dictionary<string, object> context)
{
Level2Method(context);
}
private static void Level2Method(Dictionary<string, object> context)
{
Level3Method(context);
}
private static void Level3Method(Dictionary<string, object> context)
{
throw new InvalidOperationException("Error in deep call stack");
}
private static bool LogAndFilter(Exception ex, Dictionary<string, object> context)
{
// This method runs before stack unwinding.
// Full call stack and local variables are still available.
Console.WriteLine($"Exception occurred: {ex.Message}");
Console.WriteLine($"Request ID: {context["RequestId"]}");
Console.WriteLine($"Full stack trace preserved: {ex.StackTrace}");
// Return true to handle the exception, false to continue search.
return ex.Message.Contains("deep call stack");
}
Mikor érdemes kivételszűrőket használni?
A következő esetekben használjon kivételszűrőket:
- A kivételek kezelése adott feltételek vagy tulajdonságok alapján.
- A hibakereséshez őrizze meg az eredeti hívásvermet.
- Mielőtt eldöntené, hogy kezeli-e a kivételeket, naplózza vagy vizsgálja meg a kivételeket.
- Ugyanazt a kivételtípust a környezet alapján eltérően kezelheti.
public static void HandleFileOperations(string filePath)
{
try
{
// Simulate file operation that might fail.
ProcessFile(filePath);
}
catch (IOException ex) when (ex.Message.Contains("access denied"))
{
Console.WriteLine("File access denied. Check permissions.");
}
catch (IOException ex) when (ex.Message.Contains("not found"))
{
Console.WriteLine("File not found. Verify the path.");
}
catch (IOException ex) when (IsNetworkPath(filePath))
{
Console.WriteLine($"Network file operation failed: {ex.Message}");
}
catch (IOException)
{
Console.WriteLine("Other I/O error occurred.");
}
}
private static void ProcessFile(string filePath)
{
// Simulate different types of file exceptions.
if (filePath.Contains("denied"))
throw new IOException("File access denied");
if (filePath.Contains("missing"))
throw new IOException("File not found");
if (IsNetworkPath(filePath))
throw new IOException("Network timeout occurred");
}
private static bool IsNetworkPath(string path)
{
return path.StartsWith(@"\\") || path.StartsWith("http");
}
Verem nyomkövetésének megőrzése
A kivételszűrők megőrzik az eredeti ex.StackTrace tulajdonságot. Ha egy catch záradék nem tudja feldolgozni a kivételt, és újradobja, az eredeti veremadatok elvesznek. A when szűrő nem oldja fel a vermet, ezért ha when van falseszűrő, az eredeti verem nyomkövetése nem változik.
A kivételszűrő megközelítés értékes azokban az alkalmazásokban, ahol a hibakeresési információk megőrzése kulcsfontosságú a problémák diagnosztizálásához.
Kivételek az aszinkron és iterátor metódusokban
Ha kivétel történik egy aszinkron függvényben, akkor a függvény hívójának propagálja, amikor a függvény eredményére vár , ahogy az alábbi példa is mutatja:
public static async Task Run()
{
try
{
Task<int> processing = ProcessAsync(-1);
Console.WriteLine("Launched processing.");
int result = await processing;
Console.WriteLine($"Result: {result}.");
}
catch (ArgumentException e)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
// Output:
// Launched processing.
// Processing failed: Input must be non-negative. (Parameter 'input')
}
private static async Task<int> ProcessAsync(int input)
{
if (input < 0)
{
throw new ArgumentOutOfRangeException(nameof(input), "Input must be non-negative.");
}
await Task.Delay(500);
return input;
}
Ha egy iterátormetódusban kivétel történik, az csak akkor propagálódik a hívónak, ha az iterátor továbblép a következő elemre.
Az try-finally utasítás
try-finally Egy utasításban a finally blokk akkor lesz végrehajtva, amikor a vezérlő elhagyja a try blokkot. A vezérlő elhagyhatja a try blokkot a következő miatt:
- normál végrehajtás,
- ugrási utasítás végrehajtása (vagyis
return, ,break,continuevagy ), vagygoto - kivétel propagálása a
tryblokkból.
Az alábbi példa egy objektum állapotának visszaállítására használja a finally blokkot, mielőtt a vezérlő elhagyja a metódust:
public async Task HandleRequest(int itemId, CancellationToken ct)
{
Busy = true;
try
{
await ProcessAsync(itemId, ct);
}
finally
{
Busy = false;
}
}
A blokk használatával finally a blokkban try használt lefoglalt erőforrásokat is törölheti.
Feljegyzés
Ha egy erőforrás típusa implementálja a felületet, vegye figyelembe az utasítástIDisposableIAsyncDisposable.using Az using utasítás biztosítja, hogy a beszerzett erőforrások törlődnek, amikor a vezérlő elhagyja az utasítást using . A fordító utasítássá alakít át egy using utasítást try-finally .
A blokk végrehajtása finally attól függ, hogy az operációs rendszer kivált-e kivétel-feloldási műveletet. Az egyetlen olyan eset, amikor finally a blokkok végrehajtása nem történik meg, a program azonnali leállításával jár. Ilyen megszüntetés történhet például a Environment.FailFast hívás vagy egy OverflowExceptionInvalidProgramException kivétel miatt. A legtöbb operációs rendszer ésszerű erőforrás-tisztítást végez a folyamat leállításának és eltávolításának részeként.
Az try-catch-finally utasítás
Mindkét utasítással try-catch-finally kezelheti a try blokk végrehajtása során esetlegesen előforduló kivételeket, és megadhatja azt a kódot, amelyet akkor kell végrehajtani, amikor a vezérlő elhagyja az utasítást try :
public async Task ProcessRequest(int itemId, CancellationToken ct)
{
Busy = true;
try
{
await ProcessAsync(itemId, ct);
}
catch (Exception e) when (e is not OperationCanceledException)
{
LogError(e, $"Failed to process request for item ID {itemId}.");
throw;
}
finally
{
Busy = false;
}
}
Ha egy catch blokk kivételt kezel, a finally blokk a blokk végrehajtása catch után lesz végrehajtva (még akkor is, ha egy másik kivétel történik a catch blokk végrehajtása során). További információ catch és finally blokkok : Az try-catch utasítás és az try-finally Utasítás szakasz.
C# nyelvspecifikáció
További információt a C# nyelvspecifikációjának alábbi szakaszaiban talál: