- Обработка исключений
- Конструкция try..catch..finally
- Обработка исключений и условные конструкции
- Перехват исключений
- Try и catch
- Последствия неперехвата исключений
- try-catch (Справочник по C#) try-catch (C# Reference)
- Исключения в асинхронных методах Exceptions in async methods
- Пример Example
- Пример двух блоков catch Two catch blocks example
- Пример асинхронного метода Async method example
- Пример Task.WhenAll Task.WhenAll example
- Спецификация языка C# C# language specification
Обработка исключений
Конструкция try..catch..finally
Иногда при выполнении программы возникают ошибки, которые трудно предусмотреть или предвидеть, а иногда и вовсе невозможно. Например, при передачи файла по сети может неожиданно оборваться сетевое подключение. такие ситуации называются исключениями . Язык C# предоставляет разработчикам возможности для обработки таких ситуаций. Для этого в C# предназначена конструкция try. catch. finally .
При использовании блока try. catch..finally вначале выполняются все инструкции в блоке try . Если в этом блоке не возникло исключений, то после его выполнения начинает выполняться блок finally . И затем конструкция try..catch..finally завершает свою работу.
Если же в блоке try вдруг возникает исключение, то обычный порядок выполнения останавливается, и среда CLR начинает искать блок catch , который может обработать данное исключение. Если нужный блок catch найден, то он выполняется, и после его завершения выполняется блок finally.
Если нужный блок catch не найден, то при возникновении исключения программа аварийно завершает свое выполнение.
Рассмотрим следующий пример:
В данном случае происходит деление числа на 0, что приведет к генерации исключения. И при запуске приложения в режиме отладки мы увидим в Visual Studio окошко, которое информирует об исключении:
В этом окошке мы видим, что возникло исключение, которое представляет тип System.DivideByZeroException , то есть попытка деления на ноль. С помощью пункта View Details можно посмотреть более детальную информацию об исключении.
И в этом случае единственное, что нам остается, это завершить выполнение программы.
Чтобы избежать подобного аварийного завершения программы, следует использовать для обработки исключений конструкцию try. catch. finally . Так, перепишем пример следующим образом:
В данном случае у нас опять же возникнет исключение в блоке try, так как мы пытаемся разделить на ноль. И дойдя до строки
выполнение программы остановится. CLR найдет блок catch и передаст управление этому блоку.
После блока catch будет выполняться блок finally.
Таким образом, программа по-прежнему не будет выполнять деление на ноль и соответственно не будет выводить результат этого деления, но теперь она не будет аварийно завершаться, а исключение будет обрабатываться в блоке catch.
Следует отметить, что в этой конструкции обязателен блок try . При наличии блока catch мы можем опустить блок finally:
И, наоборот, при наличии блока finally мы можем опустить блок catch и не обрабатывать исключение:
Однако, хотя с точки зрения синтаксиса C# такая конструкция вполне корректна, тем не менее, поскольку CLR не сможет найти нужный блок catch, то исключение не будет обработано, и программа аварийно завершится.
Обработка исключений и условные конструкции
Ряд исключительных ситуаций может быть предвиден разработчиком. Например, пусть программа предусматривает ввод числа и вывод его квадрата:
Если пользователь введет не число, а строку, какие-то другие символы, то программа выпадет в ошибку. С одной стороны, здесь как раз та ситуация, когда можно применить блок try..catch , чтобы обработать возможную ошибку. Однако гораздо оптимальнее было бы проверить допустимость преобразования:
Метод Int32.TryParse() возвращает true , если преобразование можно осуществить, и false — если нельзя. При допустимости преобразования переменная x будет содержать введенное число. Так, не используя try. catch можно обработать возможную исключительную ситуацию.
С точки зрения производительности использование блоков try..catch более накладно, чем применение условных конструкций. Поэтому по возможности вместо try..catch лучше использовать условные конструкции на проверку исключительных ситуаций.
Перехват исключений
Принимая во внимание, что .NET Framework включает большое количество предопределенных классов исключений, возникает вопрос: как их использовать в коде для перехвата ошибочных условий? Для того чтобы справиться с возможными ошибочными ситуациями в коде C#, программа обычно делится на блоки трех разных типов:
Блоки try инкапсулируют код, формирующий часть нормальных действий программы, которые потенциально могут столкнуться с серьезными ошибочными ситуациями.
Блоки catch инкапсулируют код, который обрабатывает ошибочные ситуации, происходящие в коде блока try. Это также удобное место для протоколирования ошибок.
Блоки finally инкапсулируют код, очищающий любые ресурсы или выполняющий другие действия, которые обычно нужно выполнить в конце блоков try или catch. Важно понимать, что этот блок выполняется независимо от того, сгенерированo исключение или нет.
Try и catch
Основу обработки исключительных ситуаций в C# составляет пара ключевых слов try и catch. Эти ключевые слова действуют совместно и не могут быть использованы порознь. Ниже приведена общая форма определения блоков try/catch для обработки исключительных ситуаций:
где ExcepType — это тип возникающей исключительной ситуации. Когда исключение генерируется оператором try, оно перехватывается составляющим ему пару оператором catch, который затем обрабатывает это исключение. В зависимости от типа исключения выполняется и соответствующий оператор catch. Так, если типы генерируемого исключения и того, что указывается в операторе catch, совпадают, то выполняется именно этот оператор, а все остальные пропускаются. Когда исключение перехватывается, переменная исключения exOb получает свое значение. На самом деле указывать переменную exOb необязательно. Так, ее необязательно указывать, если обработчику исключений не требуется доступ к объекту исключения, что бывает довольно часто. Для обработки исключения достаточно и его типа.
Следует, однако, иметь в виду, что если исключение не генерируется, то блок оператора try завершается как обычно, и все его операторы catch пропускаются. Выполнение программы возобновляется с первого оператора, следующего после завершающего оператора catch. Таким образом, оператор catch выполняется лишь в том случае, если генерируется исключение.
Давайте рассмотрим пример, в котором будем обрабатывать исключение, возникающее при делении числа на 0:
Данный простой пример наглядно иллюстрирует обработку исключительной ситуации при делении на 0 (DivideByZeroException), а так же пользовательскую ошибку при вводе не числа (FormatException).
Последствия неперехвата исключений
Перехват одного из стандартных исключений, как в приведенном выше примере, дает еще одно преимущество: он исключает аварийное завершение программы. Как только исключение будет сгенерировано, оно должно быть перехвачено каким-то фрагментом кода в определенном месте программы. Вообще говоря, если исключение не перехватывается в программе, то оно будет перехвачено исполняющей системой. Но дело в том, что исполняющая система выдаст сообщение об ошибке и прервет выполнение программы.
Например, если убрать из предыдущего примера исключение FormatException, то при вводе неккоректной строки, IDE-среда VisualStudio выдаст предупреждающее сообщение:
Такие сообщения об ошибках полезны для отладки программы, но, по меньшей мере, нежелательны при ее использовании на практике! Именно поэтому так важно организовать обработку исключительных ситуаций в самой программе.
try-catch (Справочник по C#) try-catch (C# Reference)
Оператор try-catch состоит из блока try , за которым следует одно или несколько предложений catch , задающих обработчики для различных исключений. The try-catch statement consists of a try block followed by one or more catch clauses, which specify handlers for different exceptions.
При возникновении исключения общеязыковая среда выполнения (CLR) ищет оператор catch , который обрабатывает это исключение. When an exception is thrown, the common language runtime (CLR) looks for the catch statement that handles this exception. Если текущий выполняемый метод не содержит такой блок catch , среда CLR выполняет поиск в методе, который вызвал текущий метод, и так далее вверх по стеку вызовов. If the currently executing method does not contain such a catch block, the CLR looks at the method that called the current method, and so on up the call stack. Если блок catch не находится, то среда CLR отображает пользователю сообщение о необработанном исключении и останавливает выполнение программы. If no catch block is found, then the CLR displays an unhandled exception message to the user and stops execution of the program.
Блок try содержит защищенный код, который может вызвать исключение. The try block contains the guarded code that may cause the exception. Этот блок выполняется, пока не возникнет исключение или пока он не будет успешно завершен. The block is executed until an exception is thrown or it is completed successfully. Например, следующая попытка приведения объекта null вызывает исключение NullReferenceException: For example, the following attempt to cast a null object raises the NullReferenceException exception:
Хотя предложение catch может использоваться без аргументов для перехвата любого типа исключения, такое использование не рекомендуется. Although the catch clause can be used without arguments to catch any type of exception, this usage is not recommended. В целом вы должны перехватывать только те исключения, после которых вы знаете, как выполнить восстановление. In general, you should only catch those exceptions that you know how to recover from. Поэтому всегда следует указывать аргумент объекта, производный от System.Exception, например: Therefore, you should always specify an object argument derived from System.Exception For example:
В одном блоке try-catch можно использовать несколько определенных предложений catch . It is possible to use more than one specific catch clause in the same try-catch statement. В этом случае важен порядок предложений catch , поскольку предложения catch проверяются по порядку. In this case, the order of the catch clauses is important because the catch clauses are examined in order. Перехватывайте более конкретные исключения перед менее конкретными. Catch the more specific exceptions before the less specific ones. Компилятор выдает ошибку, если вы расположили блоки catch в таком порядке, что последующий блок может быть никогда не достигнут. The compiler produces an error if you order your catch blocks so that a later block can never be reached.
Использование аргументов catch представляет один из способов фильтрации исключений, которые требуется обработать. Using catch arguments is one way to filter for the exceptions you want to handle. Вы также можете использовать фильтр исключений, который дополнительно проверяет исключение, чтобы решить, следует ли его обрабатывать. You can also use an exception filter that further examines the exception to decide whether to handle it. Если фильтр исключений возвращает значение false, поиск обработчика продолжается. If the exception filter returns false, then the search for a handler continues.
Фильтры исключений предпочтительнее перехвата и повторного вызова (объясняется ниже), поскольку фильтры оставляют стек в целости и сохранности. Exception filters are preferable to catching and rethrowing (explained below) because filters leave the stack unharmed. Если последующий обработчик разгружает стек, вы можете увидеть, откуда изначально произошло исключение, а не только последнее место, в котором оно было повторно вызвано. If a later handler dumps the stack, you can see where the exception originally came from, rather than just the last place it was rethrown. Обычно выражения фильтра исключений используются для ведения журнала. A common use of exception filter expressions is logging. Вы можете создать фильтр, который всегда возвращает значение false, а также записывает выходной результат в журнал, чтобы регистрировать исключения в журнале по мере их поступления без необходимости их обработки и повторного вызова. You can create a filter that always returns false that also outputs to a log, you can log exceptions as they go by without having to handle them and rethrow.
Оператор throw, включенный в блок catch , позволяет заново вызвать исключение, перехваченное блоком catch . A throw statement can be used in a catch block to re-throw the exception that is caught by the catch statement. В следующем примере извлекаются сведения об источнике из исключения IOException, а затем это исключение вызывается для родительского метода. The following example extracts source information from an IOException exception, and then throws the exception to the parent method.
Вы можете перехватывать одно исключение и вызывать другое исключение. You can catch one exception and throw a different exception. При этом следует указать перехватываемое исключение как внутреннее, как показано в следующем примере. When you do this, specify the exception that you caught as the inner exception, as shown in the following example.
Вы также можете повторно вызывать исключение при выполнении указанного условия, как показано в следующем примере. You can also re-throw an exception when a specified condition is true, as shown in the following example.
Вы можете использовать фильтр исключений, чтобы получить тот же результат, но проще (при этом не изменяя стек, как описано ранее в этом документе). It is also possible to use an exception filter to get a similar result in an often cleaner fashion (as well as not modifying the stack, as explained earlier in this document). В следующем примере показано такое же поведение для вызывающих объектов, как и в предыдущем примере. The following example has a similar behavior for callers as the previous example. Эта функция отправляет исключение InvalidCastException обратно вызывающему объекту, если e.Data имеет значение null . The function throws the InvalidCastException back to the caller when e.Data is null .
В блоке try инициализируйте только те переменные, которые в нем объявлены. From inside a try block, initialize only variables that are declared therein. В противном случае до завершения выполнения блока может возникнуть исключение. Otherwise, an exception can occur before the execution of the block is completed. Например, в следующем примере кода переменная n инициализируется внутри блока try . For example, in the following code example, the variable n is initialized inside the try block. Попытка использовать данную переменную вне этого блока try в инструкции Write(n) приведет к ошибке компилятора. An attempt to use this variable outside the try block in the Write(n) statement will generate a compiler error.
Дополнительные сведения о перехвате исключений см. в разделе try-catch-finally. For more information about catch, see try-catch-finally.
Исключения в асинхронных методах Exceptions in async methods
Асинхронный метод помечается модификатором async и обычно содержит одно или несколько выражений или инструкций await. An async method is marked by an async modifier and usually contains one or more await expressions or statements. Выражение await применяет оператор await к Task или Task . An await expression applies the await operator to a Task or Task .
Когда управление достигает await в асинхронном методе, выполнение метода приостанавливается до завершения выполнения ожидающей задачи. When control reaches an await in the async method, progress in the method is suspended until the awaited task completes. После завершения задачи выполнение в методе может быть возобновлено. When the task is complete, execution can resume in the method. Дополнительные сведения см. в разделе Асинхронное программирование с использованием ключевых слов async и await. For more information, see Asynchronous programming with async and await.
Завершенная задача, к которой применяется await , может находиться в состоянии сбоя из-за необработанного исключения в методе, который возвращает эту задачу. The completed task to which await is applied might be in a faulted state because of an unhandled exception in the method that returns the task. Ожидание задачи вызывает исключение. Awaiting the task throws an exception. Задача также может завершиться в отмененном состоянии, если отменяется асинхронный процесс, возвращающий эту задачу. A task can also end up in a canceled state if the asynchronous process that returns it is canceled. Ожидание отмененной задачи вызывает OperationCanceledException . Awaiting a canceled task throws an OperationCanceledException .
Для перехвата исключения ожидайте задачу в блоке try и перехватывайте это исключение в соответствующем блоке catch . To catch the exception, await the task in a try block, and catch the exception in the associated catch block. См. пример в разделе Пример асинхронного метода. For an example, see the Async method example section.
Задача может быть в состоянии сбоя, если в ожидаемом асинхронном методе произошло несколько исключений. A task can be in a faulted state because multiple exceptions occurred in the awaited async method. Например, задача может быть результатом вызова метода Task.WhenAll. For example, the task might be the result of a call to Task.WhenAll. При ожидании такой задачи перехватывается только одно из исключений и невозможно предсказать, какое исключение будет перехвачено. When you await such a task, only one of the exceptions is caught, and you can’t predict which exception will be caught. См. пример в разделе Пример Task.WhenAll. For an example, see the Task.WhenAll example section.
Пример Example
В следующем примере блок try содержит вызов метода ProcessString , который может вызвать исключение. In the following example, the try block contains a call to the ProcessString method that may cause an exception. Предложение catch содержит обработчик исключений, который просто отображает сообщение на экране. The catch clause contains the exception handler that just displays a message on the screen. Когда оператор throw вызывается из ProcessString , система осуществляет поиск оператора catch и отображает сообщение Exception caught . When the throw statement is called from inside ProcessString , the system looks for the catch statement and displays the message Exception caught .
Пример двух блоков catch Two catch blocks example
В следующем примере используются два блока catch и перехватывается наиболее конкретное исключение, поступившее первым. In the following example, two catch blocks are used, and the most specific exception, which comes first, is caught.
Чтобы перехватить наименее конкретное исключение, можно заменить оператор throw в ProcessString следующим оператором: throw new Exception() . To catch the least specific exception, you can replace the throw statement in ProcessString with the following statement: throw new Exception() .
Если в этом примере первым поместить блок catch для перехвата наименее конкретного исключения, то появится следующее сообщение об ошибке: A previous catch clause already catches all exceptions of this or a super type (‘System.Exception’) . If you place the least-specific catch block first in the example, the following error message appears: A previous catch clause already catches all exceptions of this or a super type (‘System.Exception’) .
Пример асинхронного метода Async method example
В следующем примере демонстрируется обработка исключений для асинхронных методов. The following example illustrates exception handling for async methods. Для перехвата исключения, вызванного асинхронной задачей, поместите выражение await в блок try и перехватывайте это исключение в блоке catch . To catch an exception that an async task throws, place the await expression in a try block, and catch the exception in a catch block.
Раскомментируйте строку throw new Exception в этом примере для демонстрации обработки исключений. Uncomment the throw new Exception line in the example to demonstrate exception handling. Для свойства IsFaulted задачи установлено значение True , для свойства Exception.InnerException задачи установлено это исключение, а исключение перехватывается в блоке catch . The task’s IsFaulted property is set to True , the task’s Exception.InnerException property is set to the exception, and the exception is caught in the catch block.
Раскомментируйте строку throw new OperationCanceledException , чтобы показать, что происходит при отмене асинхронного процесса. Uncomment the throw new OperationCanceledException line to demonstrate what happens when you cancel an asynchronous process. Для свойства IsCanceled задачи устанавливается значение true , и исключение перехватывается в блоке catch . The task’s IsCanceled property is set to true , and the exception is caught in the catch block. В некоторых условиях, которые неприменимы в данном примере, для свойства IsFaulted задачи устанавливается значение true , а для IsCanceled устанавливается значение false . Under some conditions that don’t apply to this example, the task’s IsFaulted property is set to true and IsCanceled is set to false .
Пример Task.WhenAll Task.WhenAll example
В следующем примере демонстрируется обработка исключений, когда несколько задач могут привести к нескольким исключениям. The following example illustrates exception handling where multiple tasks can result in multiple exceptions. Блок try ожидает задачу, которая возвращается вызовом метода Task.WhenAll. The try block awaits the task that’s returned by a call to Task.WhenAll. Эта задача завершается после завершения трех задач, к которым применяется WhenAll. The task is complete when the three tasks to which WhenAll is applied are complete.
Каждая из трех задач вызывает исключение. Each of the three tasks causes an exception. Блок catch выполняет итерацию по исключениям, которые обнаруживаются в свойстве Exception.InnerExceptions задачи, возвращенной методом Task.WhenAll. The catch block iterates through the exceptions, which are found in the Exception.InnerExceptions property of the task that was returned by Task.WhenAll.
Спецификация языка C# C# language specification
Дополнительные сведения см. в разделе Оператор try в документации Спецификация C# 6.0. For more information, see The try statement section of the C# language specification.