Обработка события: автоматическое создание обработчиков.
регистрация
Событие — элемент класса, позволяющий ему посылать другим объектам уведомления об изменении своего состояния.
Чтобы стать наблюдателем, объект должен иметь обработчик события и зарегистрировать его в объекте-источнике.
Наблюдатель 1:
(реакция на это событие)
Источник:
- Описано событие OOPS
- Иниц-я события OOPS
Наблюдатель 2:
- Обработчик события OOPS
(реакция на это событие)
Например, нажата кнопка в окне, и несколько эл-тов должны что-то при этом сделать (обновить свое состояние, etc).
Наблюдатель 3:
- Обработчик события OOPS
(реакция на это событие)
2
События сигнализируют системе о том, что произошло определенное действие.
События объявляются в классе с помощью ключевого слова event.
Есть события, которые вызываются самой программой, а есть такие, которые инициализирует пользователь, через нажатие, например, кнопки, представленной в графическом интерфейсе приложения. События не
остаются сами по себе существовать. На них требуется какая-то реакция.
В ответ на произошедшее событие должна запускаться какая-то другая программа, в теле которой станет происходить обработка информации, связанной с реакцией на данное событие . Такая запускаемая программа называется обработчиком события.
class Subj { // -------------- Класс-источник события public event EventHandler Oops ; // Описание события //станд. типа
public void CryOops()
{ // Метод, инициирующий событие
Console.WriteLine( "OOPS!" ); if ( Oops != null ) Oops( this, null );
}
}
class Obs { // --------------- Класс-наблюдатель
public void OnOops ( object sender, EventArgs e )
{
// Обработчик события
Console.WriteLine( « Оййй! " );
}
}
OOPS!
Оййй!
Оййй!
Внешний код может работать с событиями единственным образом: добавлять обработчики в список или удалять их, поскольку вне класса могут использоваться только операции += и -=. Тип результата этих операций — void, в отличие от операций сложного присваивания для арифметических типов. Иного способа доступа к списку обработчиков нет.
Внутри класса, в котором описано событие, с ним можно обращаться, как с обычным полем, имеющим тип делегата: использовать операции отношения, присваивания и т. д. Значение события по умолчанию — null. Например, в методе CryOops выполняется проверка на null для того, чтобы избежать генерации исключения System.NullReferenceException.
В библиотеке .NET описано огромное количество стандартных делегатов, предназначенных для реализации механизма обработки событий. Большинство этих классов оформлено по одним и тем же правилам:
имя делегата заканчивается суффиксом EventHandler;
делегат получает два параметра:
первый параметр задает источник события и имеет тип object;
второй параметр задает аргументы события и имеет тип EventArgs или производный от него.
Если обработчикам события требуется специфическая информация о событии, то для этого создают класс, производный от стандартного класса EventArgs, и добавляют в него необходимую информацию. Если делегат не использует такую информацию, можно не описывать делегата и собственный тип аргументов, а обойтись стандартным классом делегата System.EventHandler.
2
class Class1 {
static void Main()
{
Subj s = new Subj();
Obs o1 = new Obs(); Obs o2 = new Obs();
s.Oops += o1. OnOops ;
// регистрация обработчика
s.Oops += o2. OnOops ;
// регистрация обработчика
s.CryOops();
}
}
Механизм событий
События построены на основе делегатов: с помощью делегатов вызываются методы-обработчики событий . Поэтому создание события в классе состоит из следующих частей:
- описание делегата, задающего сигнатуру обработчиков событий; описание события; описание метода (методов), инициирующих событие.
- описание делегата, задающего сигнатуру обработчиков событий;
- описание события;
- описание метода (методов), инициирующих событие.
Синтаксис события:
[ атрибуты ] [ спецификаторы ] event тип имя
Пример:
public delegate void Del( object o ); // объявление делегата
class A
{
public event Del Oops; // объявление события
...
}
2
Обработка событий выполняется в классах-получателях сообщения. Для этого в них описываются методы-обработчики событий , сигнатура которых соответствует типу делегата. Каждый объект (не класс!), желающий получать сообщение, должен зарегистрировать в объекте-отправителе этот метод.
Внешний код может работать с событиями единственным образом: добавлять обработчики в список или удалять их, поскольку вне класса могут использоваться только операции += и -=. Тип результата этих операций — void, в отличие от операций сложного присваивания для арифметических типов. Другого способа доступа к списку обработчиков нет.
Пример:
public delegate void Del() ; // объявление делегата
class Subj // класс-источник
{
public event Del Oops; // объявление события
2
public void CryOops() // метод, инициирующий событие
{
Console.WriteLine( "OOPS!" ) ; if ( Oops != null ) Oops() ;
}
}
class ObsA // класс-наблюдатель
{
public void Do(); // реакция на событие источника
{
Console.WriteLine( "Вижу , что OOPS!" );
}
}
class ObsB // класс-наблюдатель
{
public static void See() // реакция на событие источника
{
Console.WriteLine( "Я тоже вижу, что OOPS!" );
}
}
class Class1
{
static void Main()
{
Subj s = new Subj(); // объект класса-источника
ObsA o1 = new ObsA() ; // объекты
ObsA o2 = new ObsA(); // класса-наблюдателя
s.Oops += new Del( o1.Do ) ; // добавление обработчиков
// к событию
s.Oops += new Del( o2.Do ) ; s.Oops += new Del( ObsB.See ) ;
s.CryOops(); // инициирование события
}
}
2
События включены во многие стандартные классы .NET, например, в классы пространства имен Windows.Forms, используемые для разработки Windows -приложений.
Обработка исключений
Ошибочные ситуации, возникающие в программе - это тоже события, которые требуют обработки. Синтаксические ошибки выявляет компилятор, а семантические и логические ошибки выявляются в процессе тестирования и отладки. Все ошибки должны быть устранены в процессе разработки приложений. Исключительная ситуация, или исключение — это возникновение непредвиденного или аварийного события, которое может порождаться некорректным использованием аппаратуры.
Например, это деление на ноль или обращение по несуществующему адресу памяти.
2
Исключения позволяют логически разделить вычислительный процесс на две части — обнаружение аварийной ситуации и ее обработка.
Возможные действия при ошибке:
- прервать выполнение программы;
- возвратить значение, означающее «ошибка»;
- вывести сообщение об ошибке и вернуть вызывающей программе некоторое приемлемое значение, которое позволит ей продолжать работу.
Исключения генерирует либо система выполнения, либо программист с помощью оператора throw.
Базовым для всех типов исключений является тип Exception . Этот тип определяет ряд свойств, с помощью которых можно получить информацию об исключении:
- InnerException : хранит информацию об исключении, которое послужило причиной текущего исключения
- Message : хранит сообщение об исключении, текст ошибки
- Source : хранит имя объекта или сборки, которое вызвало исключение
- StackTrace : возвращает строковое представление стека вызывов, которые привели к возникновению исключения
- TargetSite : возвращает метод, в котором и было вызвано исключение.
Тип Exception является базовым типом для всех исключений, то выражение catch (Exception ex) будет обрабатывать все исключения, которые могут возникнуть.
Есть более специализированные типы исключений, которые предназначены для обработки каких-то определенных видов исключений.
На следующем слайде приведены некоторые стандартные исключения
Имя
Пояснение
ArithmeticException
Ошибка в арифметических операциях или преобразованиях (является предком DivideBeZeroException и OverFlowException)
DivideByZero Exception
Попытка деления на ноль
Format Exception
Попытка передать в метод аргумент неверного формата
IndexOutOfRangeException
Индекс массива выходит за границы диапазона
InvalidCastException
Ошибка преобразования типа
OutOfMemoryException
Недостаточно памяти для создания нового объекта
OverFlow Exception
Переполнение при выполнении арифметических операций
StackOverFlow Exception
Переполнение стека
Оператор try
Оператор содержит три части:
- контролируемый блок — составной оператор, начинающийся ключевым словом try . В контролируемый блок включаются потенциально опасные операторы программы. Все функции, прямо или косвенно вызываемые из блока, также считаются ему принадлежащими;
- один или несколько обработчиков исключений — блоков catch , в которых описывается, как обрабатываются ошибки различных типов;
- блок завершения finally , выполняемый независимо от того, возникла ли ошибка в контролируемом блоке.
Синтаксис оператора try :
try { блок } [ catch { блоки } ] [ finally { блок } ]
Механизм обработки исключений
- Обработка исключения начинается с появления ошибки. Функция или операция, в которой возникла ошибка, генерируют исключение;
- Выполнение текущего блока прекращается, отыскивается соответствующий обработчик исключения, и ему передается управление.
- В любом случае (была ошибка или нет) выполняется блок finally , если он присутствует.
- Если обработчик не найден, вызывается стандартный обработчик исключения или при возникновении исключения программа аварийно завершает свое выполнение.
Возникло исключение, которое представляет тип System.DivideByZeroException, то есть попытка деления на ноль. С помощью пункта View Details можно посмотреть более детальную информацию об исключении.
В этом случае происходит аварийное завершение программы. Чтобы избежать подобного аварийного завершения программы, следует использовать для обработки исключений конструкцию try...catch...finally.
class Program
{
static void Main(string[] args)
{
try
{
int x = 5;
int y = x / 0;
Console.WriteLine($"Результат: {y}");
}
catch
{
Console.WriteLine("Возникло исключение!");
}
finally
{
Console.WriteLine("Блок finally");
}
Console.WriteLine("Конец программы");
Console.Read();
}
}
В данном случае опять же возникнет исключение в блоке try, так как идет делениеь на ноль. И дойдя до строки
int y = x / 0;
Обработчики исключений должны располагаться непосредственно за блоком try . Они начинаются с ключевого слова catch , за которым в скобках следует тип обрабатываемого исключения. Можно записать один или несколько обработчиков в соответствии с типами обрабатываемых исключений.
Блоки catch просматриваются в том порядке, в котором они записаны, пока не будет найден соответствующий типу выброшенного исключения.
Существуют три формы записи обработчиков:
catch( тип имя параметра ) { ... /* тело обработчика */ }
catch( тип ) { ... /* тело обработчика */ }
catch { ... /* тело обработчика */ }
Первая форма применяется, когда имя параметра используется в теле обработчика для выполнения каких-либо действий.
Вторая форма не предполагает использования информации об исключении, играет роль только его тип.
Третья форма применяется для перехвата всех исключений. Так как обработчики просматриваются в том порядке, в котором они записаны, обработчик третьего типа (он может быть только один) следует помещать после всех остальных.
Если исключение в контролируемом блоке не возникло, все обработчики пропускаются.
В любом случае, произошло исключение или нет, управление передается в блок завершения finally (если он существует), а затем — первому оператору, находящемуся непосредственно за оператором try .
Операторы try могут многократно вкладываться друг в друга. Исключение, которое возникло во внутреннем блоке try и не было перехвачено соответствующим блоком catch, передается на верхний уровень, где продолжается поиск подходящего обработчика. Этот процесс называется распространением исключения .
Пример 1:
Try
{
// Контролируемый блок
}
catch ( OverflowException e )
{
// Обработка переполнения
}
catch ( DivideByZeroException )
{
// Обработка деления на 0
}
сatch
{
// Обработка всех остальных исключений
}
Пример 2 – Проверка ввода
static void Main()
{
try
{
Console.WriteLine( "Введите напряжение:" );
double u = double.Parse( Console.ReadLine() );
Console.WriteLine( "Введите сопротивление:" );
double r = double.Parse( Console.ReadLine() );
double i = u / r;
Console.WriteLine( " Сила тока - " + i );
}
catch ( FormatException )
{
Console . WriteLine ( "Неверный формат ввода!" );
}
catch // общий случай
{
Console.WriteLine( " Неопознанное исключение " );
}
}
Фильтры исключений
Фильтры исключений позволяют обрабатывать исключения в зависимости от определенных условий. Для их применения после выражения catch идет выражение when, после которого в скобках указывается условие:
catch when(условие)
{
…
}
Здесь обработка исключения в блоке catch производится только, если условие в выражении when истинно. Например:
int x = 1;
int y = 0;
try
{
int result = x / y;
}
catch(DivideByZeroException) when (y==0 && x == 0)
{
Console.WriteLine("y не должен быть равен 0");
}
catch(DivideByZeroException ex)
{
Console.WriteLine(ex.Message);
}
В примере возникает исключение, так как y=0. Здесь два блока catch, и оба они обрабатывают исключения типа DivideByZeroException, то есть по сути все исключения, генерируемые при делении на ноль. Но для первого блока указано условие y == 0 && x == 0, то оно не будет обрабатывать исключение - условие, указанное после оператора when возвращает false. Поэтому CLR будет дальше искать соответствующие блоки catch далее и для обработки исключения выберет второй блок catch. В итоге если убрать второй блок catch, то исключение вообще не будет обрабатываться.
Оператор throw
До сих пор рассматривались исключения, которые генерирует среда выполнения C#, но это может сделать и сам программист. Для генерации исключения используется оператор throw с параметром, определяющим вид исключения.
Синтаксис:
throw [ выражение ];
Параметр должен быть объектом, порожденным от стандартного класса System.Exception . Этот объект используется для передачи информации об исключении его обработчику.
Форма без параметра применяется только внутри блока catch для повторной генерации исключения. Тип выражения, стоящего после throw , определяет тип исключения.
Пример: throw new DivideByZeroException();
Здесь после слова throw записано выражение, создающее объект стандартного класса "ошибка при делении на 0" с помощью операции new .
При генерации исключения выполнение текущего блока прекращается и происходит поиск соответствующего обработчика с передачей ему управления. Обработчик считается найденным, если тип объекта, указанного после throw , либо тот же, что задан в параметре catch , либо является производным от него.
Пример генерации исключения
Пусть в программе происходит ввод строки, и мы хотим,
чтобы, если длина строки будет больше 6 символов,
возникало исключение:
static void Main(string[] args)
{
try
{
6) { throw new Exception("Длина строки больше 6 символов"); } } catch (Exception e) { Console.WriteLine($" Ошибка : {e.Message}"); } Console.Read(); } " width="640"
Console.Write(" Введите строку : ");
string message = Console.ReadLine();
if (message.Length 6)
{
throw new Exception("Длина строки больше 6 символов");
}
}
catch (Exception e)
{
Console.WriteLine($" Ошибка : {e.Message}");
}
Console.Read();
}
После оператора throw указывается объект исключения, через конструктор которого можно передать сообщение об ошибке. Вместо типа Exception можно использовать объект любого другого типа исключений.
Затем в блоке catch сгенерированное исключение будет обработано .
Можно генерировать исключения в любом месте программы. Существует также и другая форма использования оператора throw , когда после данного оператора не указывается объект исключения. В подобном виде оператор throw может использоваться только в блоке catch .
Пример:
try
{
try
{
6) { throw new Exception("Длина строки больше 6 символов"); } } catch { Console.WriteLine("Возникло исключение"); throw; } } catch (Exception ex) { Console.WriteLine(ex.Message); } " width="640"
Console.Write(" Введите строку : ");
string message = Console.ReadLine();
if (message.Length 6)
{
throw new Exception("Длина строки больше 6 символов");
}
}
catch
{
Console.WriteLine("Возникло исключение");
throw;
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
При вводе строки с длиной больше 6 символов возникнет исключение, которое будет обработано внутренним блоком catch. Однако поскольку в этом блоке используется оператор throw, то исключение будет передано дальше внешнему блоку catch.