Instrukcje obsługi wyjątków — throw
, try-catch
, try-finally
i try-catch-finally
Instrukcje throw
i try
służą do pracy z wyjątkami. Użyj instrukcji , throw
aby zgłosić wyjątek. Użyj instrukcji , try
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
throw
wyrażenia , aby zgłosić, ArgumentException gdy przekazana tablicaargs
jest 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 ,
throw
aby 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 ,
throw
aby 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ą.
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 finally
try
opuszcza blok. Kontrolka try
może pozostawić blok w wyniku
- normalne wykonywanie,
- wykonywanie instrukcji skoku (czyli,
return
,break
,continue
lubgoto
) - propagacja wyjątku z
try
bloku.
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ę using
.IAsyncDisposable IDisposable 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 try-catch
odpowiednio instrukcje i try-finally
sekcje instrukcji.finally
specyfikacja języka C#
Aby uzyskać więcej informacji, zobacz następujące sekcje specyfikacji języka C#: