try-catch (odwołanie w C#)

Instrukcja try-catch składa się z bloku, po którym następuje co najmniej jedna klauzula, która try określa procedury obsługi dla różnych catch wyjątków.

Gdy zostanie zgłoszony wyjątek, środowisko uruchomieniowe języka wspólnego (CLR) szuka catch instrukcji, która obsługuje ten wyjątek. Jeśli metoda aktualnie wykonująca nie zawiera takiego bloku, CLR przeszukuje metodę, która wywołała bieżącą metodę i tak dalej stos catch wywołań. Jeśli blok nie zostanie znaleziony, clr wyświetla użytkownikowi nieobsługiwany komunikat o wyjątku i zatrzymuje catch wykonywanie programu.

Blok try zawiera kod zabezpieczony, który może spowodować wyjątek. Blok jest wykonywany do momentu wygenerowania wyjątku lub pomyślnego ukończenia. Na przykład następująca próba rzutowania null obiektu zgłasza NullReferenceException wyjątek:

object o2 = null;
try
{
    int i2 = (int)o2;   // Error
}

Mimo że catch klauzula może być używana bez argumentów do przechwytania dowolnego typu wyjątku, to użycie nie jest zalecane. Ogólnie rzecz biorąc, należy przechwytować tylko te wyjątki, z których wiadomo, jak je odzyskać. W związku z tym należy zawsze określić argument obiektu pochodzący z System.Exception na przykład:

catch (InvalidCastException e)
{
}

Istnieje możliwość użycia więcej niż jednej konkretnej catch klauzuli w tej samej instrukcji try-catch. W tym przypadku kolejność klauzul jest ważna, ponieważ catch catch klauzule są sprawdzane w porządek. Przechwyć bardziej szczegółowe wyjątki przed mniej specyficznymi wyjątkami. Kompilator generuje błąd w przypadku uporządkowania bloków catch tak, aby nigdy nie można było osiągnąć późniejszego bloku.

Używanie catch argumentów jest jednym ze sposobów filtrowania wyjątków, które mają być obsługiwane. Można również użyć filtru wyjątku, który dokładniej analizuje wyjątek, aby zdecydować, czy ma być obsługiwany. Jeśli filtr wyjątków zwraca wartość false, wyszukiwanie programu obsługi będzie kontynuowane.

catch (ArgumentException e) when (e.ParamName == "…")
{
}

Filtry wyjątków są preferowane do przechwycania i ponownego rzesze (wyjaśnione poniżej), ponieważ filtry pozostawiają stos bez współujść. Jeśli później program obsługi zrzuci stos, można zobaczyć, skąd pierwotnie pochodził wyjątek, a nie tylko ostatnie miejsce, w którym został ponownie wywołany. Typowe zastosowanie wyrażeń filtrowania wyjątków to rejestrowanie. Można utworzyć filtr, który zawsze zwraca wartość false, która również zwraca dane wyjściowe do dziennika. Wyjątki można rejestrować w ich przypadku bez konieczności ich obsługi i ponownego tworzenia.

Instrukcja throw może być używana w bloku do catch ponownego zgłaszania wyjątku przechwyconego przez catch instrukcje . Poniższy przykład wyodrębnia informacje o źródle z wyjątku, a następnie zgłasza IOException wyjątek do metody nadrzędnej.

catch (FileNotFoundException e)
{
    // FileNotFoundExceptions are handled here.
}
catch (IOException e)
{
    // Extract some information from this exception, and then
    // throw it to the parent method.
    if (e.Source != null)
        Console.WriteLine("IOException source: {0}", e.Source);
    throw;
}

Można przechwycić jeden wyjątek i zgłosić inny wyjątek. Gdy to zrobisz, określ wyjątek przechwycony jako wyjątek wewnętrzny, jak pokazano w poniższym przykładzie.

catch (InvalidCastException e)
{
    // Perform some action here, and then throw a new exception.
    throw new YourCustomException("Put your error message here.", e);
}

Można również ponownie zgłosić wyjątek, gdy określony warunek ma wartość true, jak pokazano w poniższym przykładzie.

catch (InvalidCastException e)
{
    if (e.Data == null)
    {
        throw;
    }
    else
    {
        // Take some action.
    }
}

Uwaga

Istnieje również możliwość użycia filtru wyjątków w celu uzyskania podobnego wyniku w sposób często bardziej przejrzysty (a także bez modyfikowania stosu, jak wyjaśniono wcześniej w tym dokumencie). Poniższy przykład ma podobne zachowanie dla wywołującego, jak w poprzednim przykładzie. Funkcja zgłasza z powrotem InvalidCastException do wywołującego, gdy e.Data jest null .

catch (InvalidCastException e) when (e.Data != null)
{
    // Take some action.
}

Z wewnątrz try bloku zaimicjuj tylko te zmienne, które są w nim zadeklarowane. W przeciwnym razie może wystąpić wyjątek przed ukończeniem wykonywania bloku. Na przykład w poniższym przykładzie kodu zmienna n jest inicjowana wewnątrz try bloku. Próba użycia tej zmiennej poza try blokiem w Write(n) instrukcji spowoduje wygenerowanie błędu kompilatora.

static void Main()
{
    int n;
    try
    {
        // Do not initialize this variable here.
        n = 123;
    }
    catch
    {
    }
    // Error: Use of unassigned local variable 'n'.
    Console.Write(n);
}

Aby uzyskać więcej informacji na temat catch, zobacz try-catch-finally.

Wyjątki w metodach asynchronicznych

Metoda asynchroniczna jest oznaczona przez modyfikator asynchroniczny i zazwyczaj zawiera co najmniej jedno wyrażenie await lub instrukcje . Wyrażenie await stosuje operator await do Task operatora lub Task<TResult> .

Gdy kontrolka osiągnie element w metodzie asynchronicznej, postęp metody jest wstrzymywany do momentu await ukończenia oczekiwanego zadania. Po zakończeniu zadania wykonywanie można wznowić w metodzie . Aby uzyskać więcej informacji, zobacz Asynchronous programming with async and await (Programowanie asynchroniczne z async i await).

Ukończone zadanie, do którego jest stosowane, może być w stanie błędu z powodu nieobsługiwanego wyjątku w metodzie, która await zwraca zadanie. Oczekiwanie na zadanie zgłasza wyjątek. Zadanie może również zostać anulowane, jeśli proces asynchroniczny, który go zwraca, zostanie anulowany. Oczekiwanie na anulowane zadanie zgłasza wyjątek OperationCanceledException .

Aby przechwyć wyjątek, zaczekaj na zadanie w try bloku i przechwyć wyjątek w skojarzonym catch bloku. Przykład można znaleźć w sekcji przykładowej metody Async.

Zadanie może być w stanie błędu, ponieważ wystąpiło wiele wyjątków w oczekiwanej metodzie asynchronicznej. Na przykład zadanie może być wynikiem wywołania do Task.WhenAll . Po oczekiwaniu na takie zadanie przechwycony jest tylko jeden z wyjątków i nie można przewidzieć, który wyjątek zostanie przechwycony. Przykład można znaleźć w sekcji Task.WhenAll przykładu.

Przykład

W poniższym przykładzie try blok zawiera wywołanie metody , które może spowodować ProcessString wyjątek. Klauzula catch zawiera program obsługi wyjątków, który po prostu wyświetla komunikat na ekranie. Gdy throw instrukcja jest wywoływana z wewnątrz , system szuka instrukcji ProcessString i wyświetla komunikat catch Exception caught .

class TryFinallyTest
{
    static void ProcessString(string s)
    {
        if (s == null)
        {
            throw new ArgumentNullException(paramName: nameof(s), message: "parameter can't be null.");
        }
    }

    public static void Main()
    {
        string s = null; // For demonstration purposes.

        try
        {
            ProcessString(s);
        }
        catch (Exception e)
        {
            Console.WriteLine("{0} Exception caught.", e);
        }
    }
}
/*
Output:
System.ArgumentNullException: Value cannot be null.
   at TryFinallyTest.Main() Exception caught.
 * */

Przykład dwóch bloków catch

W poniższym przykładzie używane są dwa bloki catch, a najbardziej specyficzny wyjątek, który jest najpierw przechwycony, jest przechwycony.

Aby przechwycić najmniej konkretny wyjątek, można zastąpić w instrukcji throw ProcessString następującą instrukcje: throw new Exception() .

Jeśli najpierw w przykładzie zostanie wyświetlony blok catch o najmniej specyficznym miejscu, zostanie wyświetlony następujący komunikat o błędzie: A previous catch clause already catches all exceptions of this or a super type ('System.Exception') .

class ThrowTest3
{
    static void ProcessString(string s)
    {
        if (s == null)
        {
            throw new ArgumentNullException(paramName: nameof(s), message: "Parameter can't be null");
        }
    }

    public static void Main()
    {
        try
        {
            string s = null;
            ProcessString(s);
        }
        // Most specific:
        catch (ArgumentNullException e)
        {
            Console.WriteLine("{0} First exception caught.", e);
        }
        // Least specific:
        catch (Exception e)
        {
            Console.WriteLine("{0} Second exception caught.", e);
        }
    }
}
/*
 Output:
 System.ArgumentNullException: Value cannot be null.
 at Test.ThrowTest3.ProcessString(String s) ... First exception caught.
*/

Przykład metody asynchronicznej

Poniższy przykład ilustruje obsługę wyjątków dla metod asynchronicznych. Aby przechwycić wyjątek zgłaszany przez zadanie asynchroniczne, umieść wyrażenie w bloku i przechwyć await try wyjątek w catch bloku.

Odkomentuj throw new Exception wiersz w przykładzie, aby zademonstrować obsługę wyjątków. Właściwość zadania jest ustawiona na wartość , właściwość zadania jest ustawiona na wyjątek, a wyjątek jest przechwycony IsFaulted True w Exception.InnerException catch bloku.

Odkomentuj throw new OperationCanceledException wiersz, aby zademonstrować, co się dzieje po anulowaniu procesu asynchronicznego. Właściwość zadania IsCanceled jest ustawiona na true , a wyjątek jest przechwycony w catch bloku. W pewnych warunkach, które nie mają zastosowania do tego przykładu, właściwość zadania jest ustawiona na IsFaulted i true jest IsCanceled ustawiona na wartość false .

public async Task DoSomethingAsync()
{
    Task<string> theTask = DelayAsync();

    try
    {
        string result = await theTask;
        Debug.WriteLine("Result: " + result);
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Exception Message: " + ex.Message);
    }
    Debug.WriteLine("Task IsCanceled: " + theTask.IsCanceled);
    Debug.WriteLine("Task IsFaulted:  " + theTask.IsFaulted);
    if (theTask.Exception != null)
    {
        Debug.WriteLine("Task Exception Message: "
            + theTask.Exception.Message);
        Debug.WriteLine("Task Inner Exception Message: "
            + theTask.Exception.InnerException.Message);
    }
}

private async Task<string> DelayAsync()
{
    await Task.Delay(100);

    // Uncomment each of the following lines to
    // demonstrate exception handling.

    //throw new OperationCanceledException("canceled");
    //throw new Exception("Something happened.");
    return "Done";
}

// Output when no exception is thrown in the awaited method:
//   Result: Done
//   Task IsCanceled: False
//   Task IsFaulted:  False

// Output when an Exception is thrown in the awaited method:
//   Exception Message: Something happened.
//   Task IsCanceled: False
//   Task IsFaulted:  True
//   Task Exception Message: One or more errors occurred.
//   Task Inner Exception Message: Something happened.

// Output when a OperationCanceledException or TaskCanceledException
// is thrown in the awaited method:
//   Exception Message: canceled
//   Task IsCanceled: True
//   Task IsFaulted:  False

Task.WhenAll , przykład

Poniższy przykład ilustruje obsługę wyjątków, w przypadku której wiele zadań może powodować wiele wyjątków. Blok try oczekuje na zadanie, które jest zwracane przez wywołanie . Task.WhenAll Zadanie jest wykonywane po zakończeniu trzech zadań, do których zastosowano whenall.

Każde z trzech zadań powoduje wyjątek. Blok iteruje przez wyjątki, które znajdują się we właściwości catch Exception.InnerExceptions zadania zwróconego przez . Task.WhenAll

public async Task DoMultipleAsync()
{
    Task theTask1 = ExcAsync(info: "First Task");
    Task theTask2 = ExcAsync(info: "Second Task");
    Task theTask3 = ExcAsync(info: "Third Task");

    Task allTasks = Task.WhenAll(theTask1, theTask2, theTask3);

    try
    {
        await allTasks;
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Exception: " + ex.Message);
        Debug.WriteLine("Task IsFaulted: " + allTasks.IsFaulted);
        foreach (var inEx in allTasks.Exception.InnerExceptions)
        {
            Debug.WriteLine("Task Inner Exception: " + inEx.Message);
        }
    }
}

private async Task ExcAsync(string info)
{
    await Task.Delay(100);

    throw new Exception("Error-" + info);
}

// Output:
//   Exception: Error-First Task
//   Task IsFaulted: True
//   Task Inner Exception: Error-First Task
//   Task Inner Exception: Error-Second Task
//   Task Inner Exception: Error-Third Task

specyfikacja języka C#

Aby uzyskać więcej informacji, zobacz sekcję instrukcji try w specyfikacji języka C#.

Zobacz też