Выбери формат для чтения
Загружаем конспект в формате docx
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Обработка исключительных ситуаций
В языке C# есть операторы, позволяющие обнаруживать и обрабатывать ошибки (исключительные ситуации), возникающие в процессе выполнения программы.
Исключительная ситуация, или исключение, — это возникновение аварийного события, которое может порождаться некорректным использованием аппаратуры или неправильной работой программы, например делением на ноль или переполнением. Обычно эти события приводят к завершению программы с системным сообщением об ошибке. C# дает программисту возможность восстановить работоспособность программы и продолжить ее выполнение. Например:
Исключения C# не поддерживают обработку асинхронных событий, таких как ошибки оборудования или прерывания, например нажатие клавиш Ctrl+C. Механизм исключений предназначен только для событий, которые могут произойти в результате работы самой программы и указываются явным образом. Исключения возникают тогда, когда некоторая часть программы не смогла сделать то, что от нее требовалось. При этом другая часть программы может попытаться сделать что-нибудь иное.
Исключения позволяют логически разделить вычислительный процесс на две части — обнаружение аварийной ситуации и ее обработка. Это важно не только для лучшей структуризации программы. Главное то, что функция, обнаружившая ошибку, может не знать, что предпринимать для ее исправления, а использующий эту функцию код может знать, что делать, но не уметь определить место возникновения. Это особенно актуально при использовании библиотечных функций и программ, состоящих из многих модулей.
Другое достоинство исключений состоит в том, что для передачи информации об ошибке в вызывающую функцию не требуется применять возвращаемое значение или параметры, поэтому заголовки функций не разрастаются. В приведённом ниже примере возникает исключительная ситуация класса IndexOutOfRangeException при вызове функции Exchange(): второй индекс (6) находится вне допустимого диапазона (0..5).
Листинг 1. Исключение: недопустимый индекс элемента массива.
using System;
namespace Example_1
{
class Program
{
static void Main(string[] args)
{
int[] m = { 1, 2, 3, 4, 5, 6 };
try
{
Exchange(1,6,m);
}
catch (IndexOutOfRangeException)
{
Console.WriteLine("Индексы выходят за границы массива");
}
Console.ReadKey();
}
static void Exchange(int x, int y, int[] v)
{
int t = v[x]; v[x] = v[y]; v[y] = t;
}
}
}
ПРИМЕЧАНИЕ
В принципе, ничто не мешает рассматривать в качестве исключений не только ошибки, но и нормальные ситуации, возникающие при обработке данных, но это не имеет преимуществ перед другими решениями, не улучшает структуру программы и не делает ее понятнее.
Исключения генерирует либо среда выполнения, либо программист с помощью оператора throw. В табл. 1 приведены наиболее часто используемые стандартные исключения, генерируемые средой. Они определены в пространстве имен System. Все они являются потомками класса Exception, а точнее, потомками его потомка SystemException. Исключения обнаруживаются и обрабатываются в операторе try.
Таблица 1. Часто используемые стандартные исключения
Имя
Описание
ArithmeticException
Ошибка в арифметических операциях или преобразованиях (является предком DivideBeZeroException и OverFlowException).
ArrayTypeMismatchException
Попытка сохранения в массиве элемента несовместимого типа.
DivideByZeroException
Попытка деления на ноль.
FormatException
Попытка передать в метод аргумент неверного формата.
IndexOutOfRangeException
Индекс массива выходит за границы диапазона.
InvalidCastException
Ошибка преобразования типа.
OutOfMemoryException
Недостаточно памяти для создания нового объекта.
OverFlowException
Переполнение при выполнении арифметических операций.
StackOverFlowException
Переполнение стека.
Оператор try
Оператор try содержит три части:
• контролируемый блок — составной оператор, предваряемый ключевым словом try. В контролируемый блок включаются потенциально опасные операторы программы. Все функции, прямо или косвенно вызываемые из блока, также считаются ему принадлежащими;
• один или несколько обработчиков исключений — блоков catch, в которых описывается, как обрабатываются ошибки различных типов;
• блок завершения finally выполняется независимо от того, возникла ошибка в контролируемом блоке или нет.
Синтаксис оператора try:
try блок [ блоки catch ] [ блок finally ]
Отсутствовать могут либо блоки catch, либо блок finally, но не оба одновременно.
Рассмотрим, каким образом реализуется обработка исключительных ситуаций.
1. Обработка исключения начинается с появления ошибки. Функция или операция, в которой возникла ошибка, генерирует исключение. Как правило, исключение генерируется не непосредственно в блоке try, а в функциях, прямо или косвенно в него вложенных.
2. Выполнение текущего блока прекращается, отыскивается соответствующий обработчик исключения, и ему передается управление.
3. Выполняется блок final1у, если он присутствует (этот блок выполняется и в том случае, если ошибка не возникла).
4. Если обработчик не найден, вызывается стандартный обработчик исключения. Его действия зависят от конфигурации среды. Обычно он выводит на экран окно с информацией об исключении и завершает текущий процесс.
ПРИМЕЧАНИЕ
Подобные окна не предназначены для пользователей программы, поэтому все исключения, которые могут возникнуть в программе, должны быть перехвачены и обработаны.
Обработчики исключений должны располагаться непосредственно за блоком try. Они начинаются с ключевого слова catch, за которым в скобках следует тип обрабатываемого исключения. Можно записать один или несколько обработчиков в соответствии с типами обрабатываемых исключений. Блоки catch просматриваются в том порядке, в котором они записаны, пока не будет найден соответствующий типу выброшенного исключения.
Синтаксис обработчиков напоминает определение функции с одним параметром — типом исключения. Существуют три формы записи:
catch( тип имя ){ ... /* тело обработчика */ }
catch( тип ) {.../* тело обработчика */ }
catch { ... /* тело обработчика */ }
Первая форма применяется, когда имя параметра используется в теле обработчика для выполнения каких-либо действий, например вывода информации об исключении.
Вторая форма не предполагает использования информации об исключении, играет роль только его тип.
Третья форма применяется для перехвата всех исключений. Так как обработчики просматриваются в том порядке, в котором они записаны, обработчик третьего типа (он может быть только один) следует помещать после всех остальных. Пример:
try
{
... // Контролируемый блок
}
catch ( OverflowException e )
{
// Обработка исключений класса OverflowException (переполнение)
}
catch ( DivideByZeroException ) {
... Обработка исключений класса DivideByZeroException (деление на 0)
}
catch
{
// Обработка всех остальных исключений
}
Если исключение в контролируемом блоке не возникло, все обработчики пропускаются.
В любом случае, произошло исключение или нет, управление передается в блок завершения finally (если он существует), а затем — первому оператору, находящемуся непосредственно за оператором try. В завершающем блоке обычно записываются операторы, которые необходимо выполнить независимо от того, возникло исключение или нет, например, закрытие файлов, с которыми выполнялась работа в контролируемом блоке, или вывод информации.
В листинге 1 приведена программа, вычисляющая силу тока по заданным напряжению и сопротивлению. Поскольку вероятность неверного набора вещественного числа довольно высока, оператор ввода включен в контролируемый блок.
ПРИМЕЧАНИЕ
Исключение, связанное с делением на ноль, для вещественных значений возникнуть не может, поэтому не проверяется. При делении на ноль будет выдан результат «бесконечность».
Листинг 2. Использование исключений для проверки ввода
using System;
namespace Example_1
{
class Program
{
static void Main(string[] args)
{
double u, i, r;
try
{
Console.WriteLine( "Введите напряжение:" );
u = double.Parse( Console.ReadLine());
Console.WriteLine( "Введите сопротивление:" );
r = double.Parse( Console.ReadLine() );
i = u / r;
Console.WriteLine( "Сила тока - " + i );
}
catch ( FormatException )
{
Console.WriteLine("Неверный формат ввода!");
}
catch // общий случай
{
Console.WriteLine( "Неопознанное исключение" );
}
Console.WriteLine("До свидания!");
}
}
}
Возникновение исключительной ситуации, вызванной нарушением формата ввода, можно предотвратить, используя вместо функции Parse() функцию TryParse(), как показано в примере ниже:
Листинг 3. Предотвращение исключения.
using System;
namespace Example_0
{
class Program
{
static void Main(string[] args)
{
double u, i, r;
Console.WriteLine( "Введите напряжение:" );
bool bu = double.TryParse(Console.ReadLine(), out u);
Console.WriteLine( "Введите сопротивление:" );
bool br = double.TryParse(Console.ReadLine(), out r);
if (bu && br)
{
i = u / r;
Console.WriteLine("Сила тока - " + i);
}
else Console.WriteLine("Ошибка формата ввода");
}
}
}
Результат работы программы при неверном вводе:
Результат работы программы в случае правильного ввода:
Операторы try могут многократно вкладываться друг в друга. Исключение, которое возникло во внутреннем блоке try и не было перехвачено соответствующим блоком catch, передается на верхний уровень, где продолжается поиск подходящего обработчика. Этот процесс называется распространением исключения.
Распространение исключений предоставляет программисту интересные возможности. Например, если на внутреннем уровне недостаточно информации для того, чтобы провести полную обработку ошибки, можно выполнить частичную обработку и сгенерировать исключение повторно, чтобы оно было обработано на верхнем уровне. Генерация исключения выполняется с помощью оператора throw.
Оператор throw
До сих пор мы рассматривали исключения, которые генерирует среда выполнения С#, но это может сделать и сам программист. Для генерации исключения используется оператор throw с параметром, определяющим вид исключения. Параметр должен быть объектом, порожденным от стандартного класса System.Exception. Этот объект используется для передачи информации об исключении его обработчику.
Оператор throw употребляется либо с параметром, либо без него:
throw [ выражение ];
Форма без параметра применяется только внутри блока catch для повторной генерации исключения. Тип выражения, стоящего после throw, определяет тип исключения, например:
throw new DivideByZeroException();
Здесь после слова throw записано выражение, создающее объект стандартного класса «ошибка при делении на 0» с помощью операции new.
При генерации исключения выполнение текущего блока прекращается и происходит поиск соответствующего обработчика с передачей ему управления. Обработчик считается найденным, если тип объекта, указанного после throw, либо тот же, что задан в параметре catch, либо является производным от него.
Рассмотрим пример, приведенный в спецификации С#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Example_2
{
class Program
{
static void Main(string[] args)
{
try { F(); }
catch ( Exception e){ Console.WriteLine( "Exception in Main: " + e.Message );}
}
static void F()
{
try
{
G(); // функция, в которой-может произойти исключение
}
catch ( Exception e )
{
Console.WriteLine( "Exception in F: " + e.Message );
e = new Exception( "E" );
throw; // повторная,генерация исключения
}
}
static void G()
{
throw new Exception( "G" ); // моделирование исключительной ситуации
}
}
}
В методе F выполняется промежуточная обработка исключения, которая заключается в том, что на консоль выводится поле Message перехваченного объекта е (об элементах класса Exception рассказывается в следующем разделе). После этого исключение генерируется заново. Несмотря на то, что в обработчике исключения создается новый объект класса Exception с измененной строкой информации, передаваемой в исключении, выбрасывается не этот объект, а тот, который был перехвачен обработчиком, поэтому результат работы программы следующий:
Exception in F: G
Exception in Main: G
Заменим оператор throw таким оператором:
throw e;
В этом случае в обработчике будет выброшено исключение, созданное в предыдущем операторе, и вывод программы изменится:
Exception in F: G
Exception in Main: F
Класс Exception
Класс Exception содержит несколько полезных свойств, с помощью которых можно получить информацию об исключении. Они перечислены в табл. 2.
Таблица 2. Свойства класса System.Exception
Свойство
Описание
НеlpLink
URL файла справки с описанием ошибки
Message
Текстовое описание ошибки. Устанавливается при создании объекта.
Свойство доступно только для чтения
Source
Имя объекта или приложения, которое сгенерировало ошибку
Свойство
StackTrace
Последовательность вызовов, которые привели к возникновению
ошибки. Свойство доступно только для чтения
InnerException
Содержит ссылку на исключение, послужившее причиной генерации
текущего исключения
TargetSite
Метод, выбросивший исключение
Создание классов исключений на основе класса Exception
Сейчас же поговорим о создании своих классов исключений в рамках объектно-ориентированного подхода.
Базовым классом для всех исключений является класс Exception. Он хранит всю информацию о произошедшем исключении. Эту информацию мы можем получить через свойства класса.
Применим свойства на примере. Допустим, у нас в программе будет ограничение по возрасту для объектов класса Person:
class Program
{
static void Main(string[] args)
{
try
{
Person p = new Person();
p.Age = 17;
}
catch (Exception ex)
{
Console.WriteLine("Ошибка: " + ex.Message);
Console.WriteLine("Метод: " + ex.TargetSite); // Void set_Age(Int32)
}
Console.ReadLine();
}
}
class Person
{
private string name;
public string Name
{
get { return name; }
set { name = value; }
}
private int age;
public int Age
{
get { return age; }
set
{
if (value < 18)
{
throw new Exception("Лицам до 18 регистрация запрещена");
}
else
{
age = value;
}
}
}
}
В классе Person при установке возраста происходит проверка, и если возраст меньше 18, то выбрасывается исключение. Класс Exception принимает в конструкторе в качестве параметра строку, которое затем передается в его свойство Message.
Но иногда удобнее использовать свои классы исключений. Например, в какой-то ситуации мы хотим обработать определенным образом только те исключения, которые относятся к классу Person. Для этих целей мы можем сделать специальный класс PersonException:
class PersonException : Exception
{
public PersonException(string message)
: base(message)
{ }
}
Описанный класс кроме пустого конструктора ничего не имеет, и то в конструкторе мы просто обращаемся к конструктору базового класса Exception, передавая в него строку message. Но теперь мы можем изменить класс Person, чтобы он выбрасывал исключение именно этого типа и соответственно в основной программе обрабатывать это исключение:
class Program
{
static void Main(string[] args)
{
try
{
Person p = new Person();
p.Age = 17;
}
catch (PersonException ex)
{
Console.WriteLine("Ошибка: " + ex.Message);
}
Console.ReadLine();
}
}
class Person
{
private int age;
public int Age
{
get { return age; }
set
{
if (value < 18)
{
throw new PersonException("Лицам до 18 регистрация запрещена");
}
else
{
age = value;
}
}
}
}