Instrukcje obsługi wyjątków — throw, try-catch, try-finallyi 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ż 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 tablica args 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 to null:

    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 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ę using .IAsyncDisposableIDisposable Instrukcja using gwarantuje, że pozyskane zasoby zostaną usunięte, gdy kontrolka opuści instrukcję using . Kompilator przekształca instrukcję using w instrukcję try-finally .

W prawie wszystkich przypadkach finally są wykonywane bloki. 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#:

Zobacz też