учебники, программирование, основы, введение в,
Путаны около станцией Комсомольская это одни из лучших и привлекательных девушек во всей.

 

Событийно управляемое программирование в .NET

Ключевым элементом для понимания природы взаимодействия объектов программы в среде вычислений является понятие события.
Под событием в математическом смысле далее будем иметь в виду соотнесение над объектом предметной области, который в рамках терминологии курса будем называть индивидом. При неформальном подходе под индивидом понимается такой объект предметной области (или языка программирования), который возможно выделить в этой области (или языке) посредством указания так называемой индивидуализирующей функции. Построение такой функции будем считать зависимым от эксперта в предметной области. Обычно такая функция имеет в качестве области своих значения истинности (а именно, «истина» и «ложь») и является истинной при аппликации к данному объекту и ложной – в противном случае.
Ранее нами было рассмотрено понятие типа как совокупности объектов. Заметим, что произвольное семейство (действительных в нашем частном случае) объектов может быть параметризовано (или, иначе, концептуализировано) не только типами, но и событиями.
В соответствии со схемой двухуровневой концептуализации, оценивающее отображение ||.|| переводит индивид h- языка (в частности, языка программирования) в h. Затем возможный (потенциальный) индивид h из семейства возможных индивидов H переводится событием i из семейства соотнесений Asg в действительный индивид h(i) из семейства действительных индивидов Ui. Аналогично, следующий шаг преобразований, управляемый событием j из семейства Asg, переводит h(i) в состояние h(i)(j.
На первый взгляд, определение понятия события в программировании существенно отличается от одноименной математической концепции.
Под событием в языке программирования обычно понимается способ внедрения того или иного фрагмента в программный код с целью изменения поведения программы.
Как только происходит изменение среды вычислений из числа представляющих интерес для разработчика или пользователя программного обеспечения, активизируется событие и выполняется соответствующий фрагмент кода.
В целом, с точки зрения практического программирования, обработка события подобна вызову процедуры, причем в роли параметров выступают те или иные характеристики среды вычислений.
Любой современный интерфейс пользователя (или, в математической терминологии, среда вычислений) построен на основе обработки событий (onClick, onMouseMove, onMouseOver и т.д.). События, которые осуществляют взаимодействие с каналами локальных сетей, операционной системой, сторонними приложениями и т.д. могут также активизироваться по времени.
В соответствии со схемой двухуровневой концептуализации, первый уровень может означать, например, инициацию пользователем события «щелчок левой кнопкой мыши», а второй – изменение состояния объекта меню при выборе соответствующего пункта меню. Как видим, сначала возможный индивид становится действительным (т.е. происходит активация события), а затем осуществляется означивание объекта программы (изменяется текущая позиция меню). Фрагментом кода программы в таком случае является метод, изменяющий текущую позицию меню, который активизируется, исходя из значения первого соотнесения (т.е. конкретизации события). Таким образом, на основе механизма событий осуществляется управление программой.
После изложения понятийного аппарата концепции событийно управляемого программирования перейдем к вопросу реализации данного механизма применительно к языку объектно-ориентированного программирования C#.
В целях реализации механизма событий в языке программирования C# предусмотрен так называемый механизм делегатов.
Заметим, что механизм делегатов в языке C# является существенным усовершенствованием ранних подходов сходной функциональности в языках программирования C и C++, известных под названием функции обратного вызова (callback function), основанных на использовании малоинформативных и потенциально небезопасных указателей в оперативной памяти.
Преимущество делегатов языка C# состоит в большей управляемости и безопасности кода (в частности, в C# существует возможность контроля соответствия типов параметров делегатов и логики вызова).
В качестве интуитивного определения понятия делегата отметим, что под делегатом понимается метод, который ведет себя как тип.
В качестве иллюстрации приведем описание типа- и переменной-делегата на языке С#:
delegate void Notifier (string sender);   
// обычное описание метода с
// ключевым словом delegate

Notifier greetings;
// описание переменной-делегата
Как видно из приведенного фрагмента программы, формально описание типов- и переменных-делегатов не отличается от традиционного описания методов и переменных.
Фундаментальное отличие переменной-делегата от ранее рассмотренных объектов языка C# состоит в том, что в качестве значения переменной-делегату можно присвоить метод. Проиллюстрируем это утверждение следующим фрагментом программы на языке C#:
void SayHello(string sender) {
Console.WriteLine(
“Hello from “ + sender);
}
greetings = new Notifier(SayHello);
Как видно из приведенного примера, на основе ранее описанного типа-делегата Notifier в соотнесении с вновь описанным методом SayHello осуществляется присваивание значения переменной greetings. Фактически данный пример изображает первый шаг концептуализации.
Проиллюстрируем далее порядок вызова переменной-делегата следующим фрагментом программы на языке C#:
greetings(“John”);
// активирует SayHello(“John”) =>
“Hello from John”
Как мы видим, означивание переменной-делегата greetings активизирует соотнесенный с ней на предыдущем шаге метод SayHello в соотнесении “John” с генерацией состояния “Hello from John”. Фактически данный пример изображает второй шаг концептуализации.
Для более наглядной иллюстрации работы механизма делегатов в языке программирования C# приведем пример фрагмента программы, характеризующего еще одно из соотнесений семейства для второго шага концептуализации:
void SayGoodBye(string sender) {
Console.WriteLine(
“Good bye from “ + sender);
}
greetings = new Notifier(SayGoodBye);
greetings(“John”);
//SayGoodBye(“John”) =>
“Good bye from John”
Как видно из приведенного примера, переменная-делегат greetings типа Notifier может принимать в качестве значения любой из методов SayHello и SayGoodBye.
В данном случае, в отличие от предыдущего примера, значением, вычисляемым в ходе выполнения метода SayGoodBye, соотнесенного с переменной-делегатом greetings, является конкретизация “Good bye from John”, полученная на основе означивания SayGoodBye(“John”).
Таким образом, становится ясным, каким образом посредством активизации (в зависимости от значения переменной-делегата) того или иного метода, становится возможным управлять поведением программы в зависимости от событий, происходящих в среде вычислений.
Для более подробного рассмотрения механизмов расширенного управления программой на языке C# посредством событий на основе делегатов, необходимо предварительно исследовать основные особенности переменных данного типа.
Прежде всего, следует отметить то обстоятельство, что переменные-делегаты могут иметь пустое значение null, что соответствует отсутствию назначенного им метода. Здесь проявляется аналогия с теорией вычислений Д. Скотта в том отношении, что любой домен непременно имеет неопределенное значение. Эта параллель наводит на предположение, что теория вычислений Д. Скотта способна адекватно формализовать событийно управляемое программирование. Оказывается, что это предположение весьма важно, а его исследование – продуктивно.
Кроме того, пустые переменные-делегаты в языке C# не подлежат вызову. При попытке осуществить обращение к такой переменной в среде программирования возникает исключительная ситуация. И снова здесь прослеживается аналогия с теорией вычислений Д. Скотта.
Еще одной особенностью переменных-делегатов является их принадлежность к классу объектов первого рода (first class object). Согласно правилам языка программирования C#, делегаты можно хранить в структурах данных, передавать как параметры и т.д.
Таким образом, делегаты как модели событий во многом сходны с функциями в математическом понимании этого слова. Следовательно, для формализации механизмов, основанных на событиях, вполне пригодны уже хорошо знакомые нам формальные системы ламбда-исчисления и комбинаторной логики.


Обсудив основные возможности описания и использования механизма делегатов в языке программирования C#, рассмотрим свойства делегатов как объектов языка более подробно. При этом будем приводить необходимые примеры на языке программирования C#.
Прежде всего, рассмотрим порядок описания языкового объекта-делегата. Проиллюстрируем обобщение создания переменной-делегата в виде формы Бэкуса-Наура (БНФ):
<описание_переменной-делегата>::=
new <тип_делегата>
(<объект>.<метод>);
При этом переменная-делегат в ходе соотнесения инициализируется конкретным методом явно указанного объекта в соответствии с типом делегата. Необходимо также отметить, что в структуре переменной-делегата хранится не только сам метод, но и его выход или приемник (receiver). Тем не менее, переменная-делегат не имеет собственных параметров:
new Notifier(myObj.SayHello);
В данном фрагменте программного кода на языке C# приведен пример описания переменной-делегата как конкретизации типа-делегата Notifier в соотнесении с уже известным нам методом SayHello определенного пользователем объекта myObj. Заметим, что присутствующий в описании объект myObj cможет быть определен явно посредством описателя this, а может быть и опущен, как, например, в следующем фрагменте программы на языке C#:
new Notifier(SayHello);
Обсудив основные возможности описания и использования механизма делегатов с динамическими методами, рассмотрим особенности статического случая.
При этом обобщенный формат описания переменной-делегата для языка программирования C# в виде БНФ примет вид:
<описание_переменной-делегата>::=
new <тип_делегата>
(<класс>.<метод>)
Таким образом, описание переменной-делегата со статическим методом в форме кода на языке программирования C# может иметь, например, следующий вид:
new Notifier(MyClass.StaticSayHello);
Существует еще ряд особенностей, характеризующих статический случай использования переменных-делегатов в языке программирования C#. Перечислим наиболее значимые из них.
Прежде всего, следует отметить, что метод в составе делегата не может быть описан как абстрактный (abstract), но может быть определен c использованием описателей как виртуальный (virtual), замещенный (override) или как экземпляр (new).
Кроме того, описание метода должно соответствовать описанию типа делегата, т.е. оба описания должны иметь одинаковое количество параметров и типы параметров (включая тип возвращаемого значения), причем виды параметров (в частности, с передачей по ссылке (ref), по значению (value), а также возвращаемые (out)) должны совпадать.
Еще одним важным свойством переменных-делегатов является их динамическая природа. Теоретически это означает, что в зависимости от (временно'го) соотнесения переменная-делегат пробегает по домену значений конкретизаций связанного с ней метода.
Практика программирования на языке C# показывает, что переменная-делегат может содержать множественные значения в одно и то же время. Проиллюстрируем это утверждение следующим фрагментом программы на языке C#:
Notifier greetings;
greetings = new Notifier(SayHello);
greetings += new Notifier(SayGoodbye);
greetings(“John”);
// “Hello from John”
// “Good bye from John”
greetings -= new Notifier(SayHello);

greetings(“John”);
// “Good bye from John”
Как видно из приведенного примера, фрагмент программы на языке C# содержит описание уже известной нам переменной-делегата greetings типа-делегата Notifier. Переменной-делегату greetings в качестве значения последовательно инкрементно присваиваются конкретизации-методы SayHello и SayGoodbye. При этом отладочный вывод значения переменной greetings с конкретизацией “John” демонстрирует семейство значений “Hello from John” и “Good bye from John”. После декрементного присваивания переменной-делегату greetings конкретизации-метода SayHello отладочный вывод значения переменной демонстрирует значение “Good bye from John”.
Заметим, что если множественный делегат является функцией, то возвращаются как значение, так и параметр последнего вызова.
Обсудив наиболее существенные для данного курса аспекты типов- и переменных-делегатов, перейдем к рассмотрению порядка использования механизма делегатов для создания событийно управляемых программ.
Проиллюстрируем подход к управлению событиями посредством механизма делегатов следующим фрагментом программного кода на языке программирования C#:
class Model {
public event Notifier notifyViews;
public void Change() {
...
notifyViews(“Model”);
}
}

class View1 {
public View1(Model m) {
m.notifyViews +=
new Notifier(this.Update1);
}
void Update1(string sender){
Console.WriteLine(
sender + “was changed”);
}
}

class View2 {
public View2(Model m){
m.notifyViews +=
new Notifier(this.Update2);
}
void Update2(string sender){
Console.WriteLine(
sender + “ was changed”);
}
}

class Test {
static void Main() {
Model m = new Model();
new View1(m);
new View2(m);
m.Change();
}
}
Как видно из приведенного примера, фрагмент программы на языке C# содержит описание класса Model с полем-событием notifyViews (описатель event) и методом Change, оповещающим через делегат о смене соотнесения. Кроме того, в данном фрагменте содержатся описания класса View1 с одноименными методом для просмотра состояния делегата посредством метода Update1, содержащего вывод на стандартное устройство отладочной информации о смене приложения-«отправителя» сообщения.
Пример завершается описанием класса View2, аналогичного классу View1, а также класса Test, который инициализирует классы View1 и View2 и запускает метод Change, инициирующий смену соотнесенийсостояний) переменных-делегатов.
Заметим, что события используются вместо обычных переменных-делегатов для увеличения уровня абстракции программного компонента, поскольку событие может активировать только тот класс, в котором оно описано.

Процесс обработки событий, как и процесс проектирования и реализации программного обеспечения в целом, не может быть свободным от ошибок и побочных эффектов. Ранее в ходе изложения уже упоминалось о таком важном свойстве доменов как наличие неопределенного элемента. Кроме того, при обсуждении семантики рассматривалась модель генерации ошибочной ситуации при невозможности связывания переменной со значением. Подобные ситуации часто возникают и при обработке событий. Рассмотрим особенности реализации оператора try языка C#, предназначенного для обнаружения и обработки ошибок, на следующем примере:
FileStream s = null;
try {
s = new FileStream(
curName, FileMode.Open);
...

    catch (FileNotFoundException e){
Console.WriteLine(
“file {0} not found”,
e.FileName);
}
catch (IOException){
Console.WriteLine(
“some IO exception
occurred”);
}
catch {
Console.WriteLine(
“some unknown error
occurred”);
}
finally if (s != null)
s.Close();
}
Как видно из примера, оператор try анализирует выход функции чтения из потокового файла. Для обработки вариантов при разборе возможных ошибочных ситуаций используется оператор catch, в данном примере генерирующий диагностические сообщения. В случае ложности всех catch-альтернатив выполняется блок операторов, следующий за словом finally (происходит штатное закрытие файла).
По результатам анализа приведенного фрагмента программы на языке C# рассмотрим основные характеристики обработки исключительных ситуаций с учетом особенностей среды вычислений Microsoft .NET.
Прежде всего, необходимо отметить то обстоятельство, что условия обнаружения catch в составе оператора try проверяются последовательно сверху вниз.
Таким образом, при полном разборе случаев разработчиком программного обеспечения одно из условий обнаружения catch всегда удовлетворяется (естественно, в том случае, если список условий не пуст).
Кроме того, синтаксис языка программирования C# разрешает опустить имя параметра exception в условии обнаружения catch.
Далее, заметим, что тип конкретизации исключительной ситуации exception должен быть выводим из базового класса системы типизации Microsoft .NET под названием System.Exception.
Наконец, важным свойством оператора является тот факт, что при отсутствии явного указания параметра exception подразумевается обработка событий в соответствии с умолчаниями, принятыми для полей и методов базового класса System.Exception системы типизации Microsoft .NET.
Для иллюстрации особенностей механизма обработки исключительных ситуаций в среде вычислений Microsoft .NET приведем фрагмент содержательного описания системного базового класса System.Exception системы типизации .NET, оформив его для наглядности в виде.
Как видно из приведенной таблицы, класс System.Exception представляет собой описание многочисленных объектов и методов, которые контролируют стандартный ход обработки исключительных ситуаций, возникающих в программных реализациях (в том числе и разработанных на языке C#). При этом каждая исключительная ситуация характеризуется уникальным диагностическим сообщением.


Таблица 23.1. Некоторые свойства и методы класса System.Exception.

Имя свойства или метода

Функции свойства или метода

Свойства:

 

e.Message

сообщение об ошибке в виде строки

e.StackTrace

просмотр стека вызова методов как строки

e.Source

приложение или объект, которые вызвали исключительную ситуацию

e.TargetSite

метод объекта, который вызвал исключительную ситуацию

...

 

Методы:

 

e.ToString()

возвращает имя исключительной ситуации

...

 

При исследовании механизмов обработки исключительных ситуаций в той или иной системе, среде или языке программирования, неизбежно возникает вопрос об источниках таких ситуаций.
Исходя из вида изученного оператора try языка программирования C#, а также из стандартных свойств и методов обработки исключений класса System.Exception среды вычислений .NET, можно предположить, что существуют явные и неявные источники возникновения исключительных ситуаций.
К неявным источникам исключений будем относить недопустимые операции, возникающие во время выполнения программы (будем считать, что компилятор работает корректно). В частности, недопустимыми операциями будем считать хорошо знакомые нам ситуации деления на нуль, выхода за границы массива, обращения к пустому (null) указателю и т.д.
Однако для реализации событийно управляемых программ в языке программирования C# существует механизм явной генерации исключений, который реализуется посредством оператора throw (приводим соответствующий фрагмент программы):
throw new FunnyException(10);
class FunnyException : ApplicationException
{
public int errorCode;
public FunnyException(int x){
errorCode = x;
}
}
Как видно из приведенного фрагмента программы, оператор throw реализует явный вызов экземпляра класса FunnyException с генерацией кода ошибки.
Заметим, что в силу высокой сложности и гетерогенной природы среды вычислений Microsoft .NET, семейство исключительных ситуаций весьма многообразно.
Для удобства систематизации, хранения, поиска и анализа исключений, а также в силу особенностей строения системы типизации Microsoft .NET, классификация исключительных ситуаций в ней организована по иерархическому принципу.
Приведем для сведения небольшой фрагмент сложной структуры иерархии стандартных исключительных ситуаций среды вычислений Microsoft .NET:
Exception
SystemException
ArithmeticException
DivideByZeroException
OverflowException
...
NullReferenceException
IndexOutOfRangeException
InvalidCastException
...
Заметим, что существует еще более общий уровень иерархии исключений, чем SystemException, а именно, уровень Exception.
В данной иерархии исключительных ситуаций перечислены наиболее типичные неявные исключения: деление на нуль и переполнение как варианты арифметического исключения.
Другим подклассом исключений являются обращение к пустой ссылке, выход за границы массива, а также неверный вызов.
Продолжим анализ иерархии исключительных ситуаций среды вычислений .NET.
ApplicationException
...// специализированные исключения
...
IOException
FileNotFoundException
DirectoryNotFoundException
...
WebException
...
Отдельный подкласс исключений составляют исключения, встречающиеся в приложениях.
Еще одним важным подклассом исключительных ситуаций являются ошибки ввода-вывода, связанные, в частности, с невозможностью найти файл или каталог.
Наконец, еще одним подклассом исключительных ситуаций, существенным для среды вычислений Microsoft .NET, ориентированной на распределенные сетевые вычисления и веб-сервисы, является подкласс веб-исключений.
Рассмотрим особенности реализации механизма реакции на исключительные ситуации в языке программирования C# и среде Microsoft .NET.
Поиск условия обнаружения исключительной ситуации в языке программирования C# реализован на основе следующего обобщенного алгоритма.
Осуществляется просмотр цепочки вызовов методов в обратном направлении до тех пор, пока не будет найден метод с соответствующим условием обнаружения catch. Если такой метод обнаружить не удается, программа завершается аварийно и выдается дамп стека (оперативной памяти).
Подчеркнем, что алгоритм поиска исключительных ситуаций не обязательно должен результативно завершиться даже для такого современного и позволяющего создавать относительно безопасный код языка программирования как C#.
Заметим, что в языке C# не существует различия между помеченными исключениями, которые необходимо отыскать, и непомеченными исключениями, в обнаружении которых нет необходимости.
Несмотря на то, что такой подход в первом приближении представляется удобным, он заведомо приводит к созданию менее устойчивого и безопасного прикладного (и тем более системного) программного кода.
Подводя итоги рассмотрения основных аспектов теории и практики событийно управляемого программирования на языке C#, можно сделать следующие выводы.
Во-первых, как и в объектно-ориентированном программировании, управление событиями (а, другими словами, поведением объектов программ) открывает возможность моделирования реальных объектов сколь угодно сложной структуры и природы.
Во-вторых, событийно ориентированное программирование обеспечивает потенциальную легкость настройки интерфейса пользователя (и его адаптации в процессе работы программы в соответствии с требованиями пользователя).
Кроме того, процедуры обработки событий представляют собой методы в терминологии ООП. Таким образом, основным предметом программирования являются сценарии, причем методы делегатов, реализованные в форме скриптов, изменяют состояние программы.
Затем, важным преимуществом программирования, ориентированного на события или скрипты, является высокий процент повторного использования программного кода. Заметим, что значительное количество событий уже реализовано в среде вычислений .NET, и нами был рассмотрен фрагмент их иерархии.
Далее, необходимо отметить такой позитивный аспект событийного программирования как гибкость реинжиниринга (т.е. разработки в обратном направлении) программного обеспечения. По сути, речь идет о концептуализации, которая является теоретической основой наших рассуждений.
Именно концептуализация составляет то строгое математическое основание, которое позволяет не только моделировать управление событиями, но и обеспечить верифицируемость создаваемого программного обеспечения.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика