• Статья
  • Чтение занимает 4 мин

Оператор try-catch состоит из блока try, за которым следует одно или несколько предложений catch, задающих обработчики для различных исключений.

При возникновении исключения общеязыковая среда выполнения (CLR) ищет оператор catch, который обрабатывает это исключение. Если текущий выполняемый метод не содержит такой блок catch, среда CLR выполняет поиск в методе, который вызвал текущий метод, и так далее вверх по стеку вызовов. Если блок catch не находится, то среда CLR отображает пользователю сообщение о необработанном исключении и останавливает выполнение программы.

Блок try содержит защищенный код, который может вызвать исключение. Этот блок выполняется, пока не возникнет исключение или пока он не будет успешно завершен. Например, следующая попытка приведения объекта null вызывает исключение NullReferenceException:

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

Хотя предложение catch может использоваться без аргументов для перехвата любого типа исключения, такое использование не рекомендуется. В целом вы должны перехватывать только те исключения, после которых вы знаете, как выполнить восстановление. Поэтому всегда следует указывать аргумент объекта, производный от System.Exception. Тип исключения должен быть как можно более конкретным, чтобы избежать неправильного приема исключений, которые обработчик исключений не сможет разрешить. Таким образом, лучше использовать конкретные исключения вместо базового типа Exception. Вот несколько примеров.

catch (InvalidCastException e)
{
    // recover from exception
}

В одном блоке try-catch можно использовать несколько определенных предложений catch. В этом случае важен порядок предложений catch, поскольку предложения catch проверяются по порядку. Перехватывайте более конкретные исключения перед менее конкретными. Компилятор выдает ошибку, если вы расположили блоки catch в таком порядке, что последующий блок может быть никогда не достигнут.

Использование аргументов catch представляет один из способов фильтрации исключений, которые требуется обработать. Вы также можете использовать фильтр исключений, который дополнительно проверяет исключение, чтобы решить, следует ли его обрабатывать. Если фильтр исключений возвращает значение false, поиск обработчика продолжается.

catch (ArgumentException e) when (e.ParamName == "…")
{
    // recover from exception
}

Фильтры исключений предпочтительнее перехвата и повторного вызова (объясняется ниже), поскольку фильтры оставляют стек в целости и сохранности. Если последующий обработчик разгружает стек, вы можете увидеть, откуда изначально произошло исключение, а не только последнее место, в котором оно было повторно вызвано. Обычно выражения фильтра исключений используются для ведения журнала. Вы можете создать фильтр, который всегда возвращает значение false, а также записывает выходной результат в журнал, чтобы регистрировать исключения в журнале по мере их поступления без необходимости их обработки и повторного вызова.

Оператор throw, включенный в блок catch, позволяет заново вызвать исключение, перехваченное блоком catch. В следующем примере извлекаются сведения об источнике из исключения IOException, а затем это исключение вызывается для родительского метода.

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;
}

Вы можете перехватывать одно исключение и вызывать другое исключение. При этом следует указать перехватываемое исключение как внутреннее, как показано в следующем примере.

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

Вы также можете повторно вызывать исключение при выполнении указанного условия, как показано в следующем примере.

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

Примечание

Вы можете использовать фильтр исключений, чтобы получить тот же результат, но проще (при этом не изменяя стек, как описано ранее в этом документе). В следующем примере показано такое же поведение для вызывающих объектов, как и в предыдущем примере. Эта функция отправляет исключение InvalidCastException обратно вызывающему объекту, если e.Data имеет значение null.

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

В блоке try инициализируйте только те переменные, которые в нем объявлены. В противном случае до завершения выполнения блока может возникнуть исключение. Например, в следующем примере кода переменная n инициализируется внутри блока try. Попытка использовать данную переменную вне этого блока try в инструкции Write(n) приведет к ошибке компилятора.

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);
}

Дополнительные сведения о перехвате исключений см. в разделе try-catch-finally.

Исключения в асинхронных методах

Асинхронный метод помечается модификатором async и обычно содержит одно или несколько выражений или инструкций await. Выражение await применяет оператор await к Task или Task<TResult>.

Когда управление достигает await в асинхронном методе, выполнение метода приостанавливается до завершения выполнения ожидающей задачи. После завершения задачи выполнение в методе может быть возобновлено. Дополнительные сведения см. в разделе Асинхронное программирование с использованием ключевых слов async и await.

Завершенная задача, к которой применяется await, может находиться в состоянии сбоя из-за необработанного исключения в методе, который возвращает эту задачу. Ожидание задачи вызывает исключение. Задача также может завершиться в отмененном состоянии, если отменяется асинхронный процесс, возвращающий эту задачу. Ожидание отмененной задачи вызывает OperationCanceledException.

Для перехвата исключения ожидайте задачу в блоке try и перехватывайте это исключение в соответствующем блоке catch. См. пример в разделе Пример асинхронного метода.

Задача может быть в состоянии сбоя, если в ожидаемом асинхронном методе произошло несколько исключений. Например, задача может быть результатом вызова метода Task.WhenAll. При ожидании такой задачи перехватывается только одно из исключений и невозможно предсказать, какое исключение будет перехвачено. См. пример в разделе Пример Task.WhenAll.

Пример

В следующем примере блок try содержит вызов метода ProcessString, который может вызвать исключение. Предложение catch содержит обработчик исключений, который просто отображает сообщение на экране. Когда оператор throw вызывается из ProcessString, система осуществляет поиск оператора 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.
 * */

Пример двух блоков catch

В следующем примере используются два блока catch и перехватывается наиболее конкретное исключение, поступившее первым.

Чтобы перехватить наименее конкретное исключение, можно заменить оператор throw в ProcessString следующим оператором: throw new Exception().

Если в этом примере первым поместить блок catch для перехвата наименее конкретного исключения, то появится следующее сообщение об ошибке: 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.
*/

Пример асинхронного метода

В следующем примере демонстрируется обработка исключений для асинхронных методов. Для перехвата исключения, вызванного асинхронной задачей, поместите выражение await в блок try и перехватывайте это исключение в блоке catch.

Раскомментируйте строку throw new Exception в этом примере для демонстрации обработки исключений. Для свойства IsFaulted задачи установлено значение True, для свойства Exception.InnerException задачи установлено это исключение, а исключение перехватывается в блоке catch.

Раскомментируйте строку throw new OperationCanceledException, чтобы показать, что происходит при отмене асинхронного процесса. Для свойства IsCanceled задачи устанавливается значение true, и исключение перехватывается в блоке catch. В некоторых условиях, которые неприменимы в данном примере, для свойства IsFaulted задачи устанавливается значение true, а для IsCanceled устанавливается значение 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

В следующем примере демонстрируется обработка исключений, когда несколько задач могут привести к нескольким исключениям. Блок try ожидает задачу, которая возвращается вызовом метода Task.WhenAll. Эта задача завершается после завершения трех задач, к которым применяется WhenAll.

Каждая из трех задач вызывает исключение. Блок catch выполняет итерацию по исключениям, которые обнаруживаются в свойстве Exception.InnerExceptions задачи, возвращенной методом 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

Спецификация языка C#

Дополнительные сведения см. в разделе Оператор try в документации Спецификация C# 6.0.

См. также

  • Справочник по C#
  • Руководство по программированию на C#
  • Ключевые слова в C#
  • Операторы try, throw и catch (C++)
  • throw
  • try-finally
  • Практическое руководство. Явное создание исключений