Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Instrukcje obsługi wyjątków —
Użyj instrukcji throw i try , aby pracować 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.
Dokumentacja języka C# zawiera ostatnio wydaną wersję języka C#. Zawiera również początkową dokumentację funkcji w publicznej wersji zapoznawczej nadchodzącej wersji językowej.
Dokumentacja identyfikuje dowolną funkcję po raz pierwszy wprowadzoną w ostatnich trzech wersjach języka lub w bieżącej publicznej wersji zapoznawczej.
Wskazówka
Aby dowiedzieć się, kiedy funkcja została po raz pierwszy wprowadzona w języku C#, zapoznaj się z artykułem dotyczącym historii wersji języka C#.
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, takich jak 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 użyj 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 . Z kolei 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 nie ma zgodnego catch bloku, clR koń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. Takie podejście może być wygodne w kilku przypadkach, w tym:
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 uruchamianego podczas opuszczania try bloku 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 środowisko uruchomieniowe sprawdza klauzule catch w określonej kolejności od góry do dołu. Co najwyżej jeden catch blok jest uruchamiany 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.
Aby ponownie zgłosić wyjątek przechwycony, użyj instrukcjithrow , 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 . Z kolei 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 uruchamiany po odłą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 go 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 debugowania ma kluczowe znaczenie dla diagnozowania błędów kodu.
Wyjątki w metodach asynchronicznych i iteratorowych
Jeśli wyjątek występuje w funkcji asynchronicznych, wyjątek 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, wyjątek 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 uruchamiany, 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 To, czy blok jest wykonywany, zależy od tego, czy system operacyjny zdecyduje się wyzwolić operację odwijającego 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
try-catch-finally Użyj instrukcji , aby obsługiwać wyjątki, które mogą wystąpić podczas wykonywania try bloku i określić kod, który musi być uruchamiany, 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;
}
}
catch Gdy blok obsługuje wyjątek, finally blok jest uruchamiany po zakończeniu 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#: