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 try az throw 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.
A C# nyelv referenciadokumentuma a C# nyelv legújabb kiadású verzióját ismerteti. Emellett a közelgő nyelvi kiadás nyilvános előzetes verziójú funkcióinak kezdeti dokumentációját is tartalmazza.
A dokumentáció azonosítja azokat a funkciókat, amelyeket először a nyelv utolsó három verziójában vagy az aktuális nyilvános előzetes verziókban vezetnek be.
Jótanács
Ha meg szeretné tudni, hogy mikor jelent meg először egy funkció a C#-ban, tekintse meg a C# nyelvi verzióelőzményeiről szóló cikket.
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 a beépített kivételosztályokat, például ArgumentOutOfRangeException vagy InvalidOperationException. 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 kiírhatja 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 szemben throw e; frissíti 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 nincs kompatibilis catch 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 a megközelítés több esetben is 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 futó kód megadására, valamint 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 futtatókörnyezet felülről lefelé haladva ellenőrzi a fogási záradékokat a megadott sorrendben. Legfeljebb egy catch blokk fut 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.
A kifogott kivétel újbóli eldobásához 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 szemben throw e; frissíti 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 kódhibá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, a kivétel 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 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átor metódusban kivétel történik, a kivétel 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 blokk akkor fut, amikor a finally 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 finally blokk végrehajtása attól függ, hogy az operációs rendszer kiváltja-e a kivétel kikapcsolási műveletét. Az egyetlen olyan eset, amikor finally a blokkok végrehajtása nem jár a program azonnali leállításával. 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ása és eltávolítása során.
Az try-catch-finally utasítás
try-catch-finally A blokk végrehajtása try során esetlegesen előforduló kivételek kezelésére és az utasítás elhagyásakor try futtatandó kód megadására egyaránt használjon utasítást:
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 catch blokk befejeződése után fut (még akkor is, ha a blokk végrehajtása catch során egy másik kivétel történik). 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: