Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Instrukcje obsługi wyjątków —
Instrukcje throw i try służą do pracy z wyjątkami. Użyj instrukcjithrow aby zgłosić wyjątek. Użyj instrukcjitry aby przechwycić wyjątki, które mogą wystąpić podczas wykonywania bloku kodu i obsługiwać je.
Instrukcja throw
Instrukcja throw zgłasza wyjątek:
if (shapeAmount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(shapeAmount), "Amount of shapes must be positive.");
}
throw e; W instrukcji wynik wyrażenia e musi być niejawnie konwertowany na System.Exception.
Można użyć wbudowanych klas wyjątków, na przykład ArgumentOutOfRangeException lub InvalidOperationException. Platforma .NET udostępnia również następujące metody pomocnicze do zgłaszania wyjątków w określonych warunkach: ArgumentNullException.ThrowIfNull i ArgumentException.ThrowIfNullOrEmpty. Można również zdefiniować własne klasy wyjątków pochodzące z klasy System.Exception. Aby uzyskać więcej informacji, zobacz Tworzenie i zgłaszanie wyjątków.
catch Wewnątrz bloku można użyć throw; instrukcji , aby ponownie zgłosić wyjątek obsługiwany przez catch blok:
try
{
ProcessShapes(shapeAmount);
}
catch (Exception e)
{
LogError(e, "Shape processing failed.");
throw;
}
Uwaga
throw; Zachowuje oryginalny ślad stosu wyjątku, który jest przechowywany we Exception.StackTrace właściwości . W przeciwieństwie do tego, throw e; aktualizuje StackTrace właściwość e.
Po wystąpieniu wyjątku środowisko uruchomieniowe języka wspólnego (CLR) wyszukuje catch blok , który może obsłużyć ten wyjątek. Jeśli aktualnie wykonywana metoda nie zawiera takiego bloku, CLR analizuje metodę catch , która nazwała bieżącą metodę, i tak dalej w górę stosu wywołań. Jeśli żaden blok nie catch zostanie znaleziony, clR zakończy wykonywanie wątku. Aby uzyskać więcej informacji, zobacz sekcję How exceptions are handled (Jak są obsługiwane wyjątki) specyfikacji języka C#.
Wyrażenie throw
Można również użyć throw jako wyrażenia. Może to być wygodne w wielu przypadkach, takich jak:
operator warunkowy. W poniższym przykładzie użyto
throwwyrażenia , aby zgłosić, ArgumentException gdy przekazana tablicaargsjest pusta:string first = args.Length >= 1 ? args[0] : throw new ArgumentException("Please supply at least one argument.");operator łączenia wartości null. W poniższym przykładzie użyto wyrażenia ,
throwaby zgłosić ArgumentNullException , kiedy ciąg do przypisania do właściwości tonull:public string Name { get => name; set => name = value ?? throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null"); }wyrażenie-bodied lambda lub metoda. W poniższym przykładzie użyto wyrażenia ,
throwaby zgłosić wartość InvalidCastException , aby wskazać, że konwersja na DateTime wartość nie jest obsługiwana:DateTime ToDateTime(IFormatProvider provider) => throw new InvalidCastException("Conversion to a DateTime is not supported.");
Instrukcja try
Instrukcję try można użyć w dowolnej z następujących form: try-catch — do obsługi wyjątków, które mogą wystąpić podczas wykonywania kodu wewnątrz try bloku, try-finally — w celu określenia kodu wykonywanego podczas opuszczania try bloku przez kontrolkę i try-catch-finally — jako kombinacji poprzednich dwóch formularzy.
Instrukcja try-catch
Użyj instrukcji try-catch , aby obsługiwać wyjątki, które mogą wystąpić podczas wykonywania bloku kodu. Umieść kod, w którym może wystąpić wyjątek wewnątrz try bloku.
Użyj klauzuli catch, aby określić podstawowy typ wyjątków, które mają być obsługiwane w odpowiednim catch bloku:
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (ArgumentException e)
{
Console.WriteLine($"Processing failed: {e.Message}");
}
Możesz podać kilka klauzul catch:
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.");
}
W przypadku wystąpienia wyjątku klauzule catch są badane w określonej kolejności od góry do dołu. Maksymalnie catch jeden blok jest wykonywany dla każdego zgłaszanego wyjątku. Jak pokazano w poprzednim przykładzie, można pominąć deklarację zmiennej wyjątku i określić tylko typ wyjątku w klauzuli catch. Klauzula catch bez żadnego określonego typu wyjątku pasuje do wyjątku i, jeśli istnieje, musi być ostatnią klauzulą catch.
Jeśli chcesz ponownie zgłosić wyjątek przechwycony, użyj instrukcji throw, jak pokazano w poniższym przykładzie:
try
{
var result = Process(-3, 4);
Console.WriteLine($"Processing succeeded: {result}");
}
catch (Exception e)
{
LogError(e, "Processing failed.");
throw;
}
Uwaga
throw; Zachowuje oryginalny ślad stosu wyjątku, który jest przechowywany we Exception.StackTrace właściwości . W przeciwieństwie do tego, throw e; aktualizuje StackTrace właściwość e.
when Filtr wyjątku
Oprócz typu wyjątku można również określić filtr wyjątku, który dodatkowo analizuje wyjątek i decyduje, czy odpowiedni catch blok obsługuje ten wyjątek. Filtr wyjątku jest wyrażeniem logicznym, które jest zgodne ze when słowem kluczowym, jak pokazano w poniższym przykładzie:
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}");
}
W poprzednim przykładzie użyto filtru wyjątku w celu zapewnienia pojedynczego catch bloku do obsługi wyjątków dwóch określonych typów.
Można podać kilka catch klauzul dla tego samego typu wyjątku, jeśli rozróżniają one filtry wyjątków. Jedna z tych klauzul może nie mieć filtru wyjątków. Jeśli taka klauzula istnieje, musi to być ostatnia z klauzul określających ten typ wyjątku.
Jeśli klauzula catch ma filtr wyjątku, może określić typ wyjątku, który jest taki sam, jak lub mniej pochodny niż typ wyjątku klauzuli catch , która pojawia się po nim. Jeśli na przykład istnieje filtr wyjątku, klauzula catch (Exception e) nie musi być ostatnią klauzulą.
Filtry wyjątków a tradycyjna obsługa wyjątków
Filtry wyjątków zapewniają znaczne korzyści w porównaniu z tradycyjnymi metodami obsługi wyjątków. Kluczową różnicą jest ocena logiki obsługi wyjątków:
-
Filtry wyjątków (
when): wyrażenie filtru jest oceniane przed odłączeniu stosu. Oznacza to, że oryginalny stos wywołań i wszystkie zmienne lokalne pozostają nienaruszone podczas oceny filtru. -
Tradycyjne
catchbloki: blok catch jest wykonywany po rozłączeniu stosu, co może spowodować utratę cennych informacji debugowania.
Oto porównanie pokazujące różnicę:
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}");
}
Zalety filtrów wyjątków
- Lepsze środowisko debugowania: ponieważ stos nie jest wywoływany do momentu dopasowania filtru, debugery mogą wyświetlać oryginalny punkt awarii ze wszystkimi zmiennymi lokalnymi bez zmian.
- Korzyści z wydajności: jeśli nie ma pasujących filtrów, wyjątek będzie nadal propagowany bez konieczności odwijania i przywracania stosu.
- Kod czyszczący: Wiele filtrów może obsługiwać różne warunki tego samego typu wyjątku bez konieczności zagnieżdżenia instrukcji if-else.
- Rejestrowanie i diagnostyka: Przed podjęciem decyzji o tym, czy należy obsłużyć wyjątek, możesz sprawdzić szczegóły wyjątku i rejestrować je:
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");
}
Kiedy należy używać filtrów wyjątków
Użyj filtrów wyjątków, jeśli musisz:
- Obsługa wyjątków na podstawie określonych warunków lub właściwości.
- Zachowaj oryginalny stos wywołań na potrzeby debugowania.
- Rejestruj lub sprawdzaj wyjątki przed podjęciem decyzji, czy je obsługiwać.
- Obsłuż ten sam typ wyjątku inaczej na podstawie kontekstu.
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");
}
Zachowywanie śledzenia stosu
Filtry wyjątków zachowują oryginalną ex.StackTrace właściwość. Jeśli klauzula catch nie może przetworzyć wyjątku i zgłosi je ponownie, oryginalne informacje o stosie zostaną utracone. Filtr when nie odwija stosu, więc jeśli when filtr to false, oryginalny ślad stosu nie zostanie zmieniony.
Metoda filtrowania wyjątków jest cenna w aplikacjach, w których zachowanie informacji o debugowaniu ma kluczowe znaczenie dla diagnozowania problemów.
Wyjątki w metodach asynchronicznych i iteratorowych
Jeśli wyjątek występuje w funkcji asynchronicznych, jest propagowany do obiektu wywołującego funkcji, gdy oczekujesz na wynik funkcji, jak pokazano w poniższym przykładzie:
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;
}
Jeśli wyjątek występuje w metodzie iteratora, jest propagowany do obiektu wywołującego tylko wtedy, gdy iterator przechodzi do następnego elementu.
Instrukcja try-finally
try-finally W instrukcji blok jest wykonywany, gdy kontrolka finallytry opuszcza blok. Kontrolka try może pozostawić blok w wyniku
W poniższym przykładzie użyto finally bloku do zresetowania stanu obiektu przed opuszczeniem metody przez kontrolkę:
public async Task HandleRequest(int itemId, CancellationToken ct)
{
Busy = true;
try
{
await ProcessAsync(itemId, ct);
}
finally
{
Busy = false;
}
}
Blok umożliwia również finally wyczyszczenie przydzielonych zasobów używanych try w bloku.
Uwaga
Jeśli typ zasobu implementuje interfejs lub, należy wziąć pod uwagę instrukcję IDisposableIAsyncDisposable .using Instrukcja using gwarantuje, że pozyskane zasoby zostaną usunięte, gdy kontrolka opuści instrukcję using . Kompilator przekształca instrukcję using w instrukcję try-finally .
finally Wykonanie bloku zależy od tego, czy system operacyjny zdecyduje się wyzwolić operację odwijenia wyjątku. Jedynymi przypadkami, w których finally bloki nie są wykonywane, są natychmiastowe zakończenie programu. Na przykład takie zakończenie może wystąpić z Environment.FailFast powodu wywołania lub OverflowException wyjątku.InvalidProgramException Większość systemów operacyjnych wykonuje rozsądne czyszczenie zasobów w ramach zatrzymywania i zwalniania procesu.
Instrukcja try-catch-finally
Instrukcja służy try-catch-finally zarówno do obsługi wyjątków, które mogą wystąpić podczas wykonywania try bloku, i określa kod, który należy wykonać, gdy kontrolka opuszcza instrukcję 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;
}
}
Gdy wyjątek jest obsługiwany przez catch blok, finally blok jest wykonywany po wykonaniu tego catch bloku (nawet jeśli podczas wykonywania catch bloku wystąpi inny wyjątek). Aby uzyskać informacje na temat catch i bloków, zobacz finallytry-catch odpowiednio instrukcje i .try-finally
specyfikacja języka C#
Aby uzyskać więcej informacji, zobacz następujące sekcje specyfikacji języka C#: