Автор работы: Пользователь скрыл имя, 28 Марта 2012 в 11:49, курс лекций
Лекции по дисциплине "Программирование"
Лекция 1. Состав языка Типы данных Переменные и операции
Лекция 2. Линейные программы
Лекция 3. Простейшие операторы. Операторы ветвления
Лекция 4. Операторы цикла и передачи управления
Лекция 5. Обработка исключительных ситуаций
Лекция 6. Классы: основные понятия Описание класса
Лекция 7. Параметры методов
Лекция 8. Конструкторы и свойства
Лекция 9. . Массивы
Лекция 10. Символы и строки
Лекция 11 Дополнительные возможности методов. Индексаторы
Лекция 12. Операции класса. Деструкторы
Лекция 13. Наследование классов
Лекция 14. Интерфейсы
Лекция 15. Стандартные интерфейсы .NET
Лекция 16. Структуры и перечисления
Лекция 17. Делегаты
Лекция 18. События
using System;
namespace ConsoleApplication1
{
public delegate void Del( object o ); // объявление делегата
class Subj
{
Del dels; // объявление экземпляра делегата
public void Register( Del d ) // регистрация делегата
{
dels += d;
}
public void OOPS()
{
Console.WriteLine( "OOPS!" );
if ( dels != null ) dels( this ); // оповещение наблюдателей
}
}
class ObsA
{
public void Do( object o ) // реакция на событие источника
{
Console.WriteLine( "Вижу, что OOPS!" );
}
}
class ObsB
{
public static void See( object o ) // реакция на событие источника
{
Console.WriteLine( "Я тоже вижу, что OOPS!" );
}
}
class Class1
{
static void Main()
{
Subj s = new Subj(); // объект класса-источника
ObsA o1 = new ObsA(); // объекты
ObsA o2 = new ObsA(); // класса-наблюдателя
s.Register( new Del( o1.Do ) ); // регистрация методов
s.Register( new Del( o2.Do ) ); // наблюдателей в источнике
s.Register( new Del( ObsB.See ) ); // ( экземпляры делегата )
s.OOPS();
}
}
}
В источнике объявляется экземпляр делегата, в этот экземпляр заносятся методы тех объектов, которые хотят получать уведомление об изменении состояния источника. Этот процесс называется регистрацией делегатов. При регистрации имя метода добавляется к списку. При наступлении «часа Х» все зарегистрированные методы поочередно вызываются через делегат.
Результат работы программы:
OOPS!
Вижу, что OOPS!
Вижу, что OOPS!
Я тоже вижу, что OOPS!
Для обеспечения обратной связи между наблюдателем и источником делегат объявлен с параметром типа object, через который в вызываемый метод передается ссылка на вызывающий объект. Следовательно, в вызываемом методе можно получать информацию о состоянии вызывающего объекта и посылать ему сообщения.
Связь «источник — наблюдатель» устанавливается во время выполнения программы для каждого объекта по отдельности. Если наблюдатель больше не хочет получать уведомления от источника, можно удалить соответствующий метод из списка делегата с помощью метода Remove или перегруженной операции вычитания, например:
public void UnRegister( Del d ) // удаление делегата
{
dels -= d;
}
Делегаты можно сравнивать на равенство и неравенство. Два делегата равны, если они оба не содержат ссылок на методы или если они содержат ссылки на одни и те же методы в одном и том же порядке. Сравнивать можно даже делегаты различных типов при условии, что они имеют один и тот же тип возвращаемого значения и одинаковые списки параметров.
С делегатами одного типа можно выполнять операции простого и сложного присваивания, например:
Del d1 = new Del( o1.Do ); // o1.Do
Del d2 = new Del( o2.Do ); // o2.Do
Del d3 = d1 + d2; // o1.Do и o2.Do
d3 += d1; // o1.Do, o2.Do и o1.Do
d3 -= d2; // o1.Do и o1.Do
Эти операции могут понадобиться, например, в том случае, если в разных обстоятельствах требуется вызывать разные наборы и комбинации наборов методов.
Делегат, как и строка string, является неизменяемым типом данных, поэтому при любом изменении создается новый экземпляр, а старый впоследствии удаляется сборщиком мусора.
Поскольку делегат является классом, его можно передавать в методы в качестве параметра. Таким образом обеспечивается функциональная параметризация: в метод можно передавать не только различные данные, но и различные функции их обработки. Функциональная параметризация применяется для создания универсальных методов и обеспечения возможности обратного вызова.
В качестве простейшего примера универсального метода можно привести метод вывода таблицы значений функции, в который передается диапазон значений аргумента, шаг его изменения и вид вычисляемой функции. Пример приведен в листинге 19.3.
Листинг 19.3. Передача делегата через список параметров
using System;
namespace ConsoleApplication1
{
public delegate double Fun( double x ); // объявление делегата
class Class1
{
public static void Table( Fun F, double x, double b )
{
Console.WriteLine( " ----- X ----- Y -----" );
while (x <= b)
{
Console.WriteLine( "| {0,8:0.000} | {1,8:0.000} |", x, F(x));
x += 1;
}
Console.WriteLine( " ---------------------" );
}
public static double Simple( double x )
{
return 1;
}
static void Main()
{
Console.WriteLine( " Таблица функции Sin " );
Table( new Fun( Math.Sin ), -2, 2 );
Console.WriteLine( " Таблица функции Simple " );
Table( new Fun( Simple ), 0, 3 );
}
}
}
Результат работы программы:
Таблица функции Sin
----- X ----- Y -----
| -2,000 | -0,909 |
| -1,000 | -0,841 |
| 0,000 | 0,000 |
| 1,000 | 0,841 |
| 2,000 | 0,909 |
---------------------
Таблица функции Simple
----- X ----- Y -----
| 0,000 | 1,000 |
| 1,000 | 1,000 |
| 2,000 | 1,000 |
| 3,000 | 1,000 |
---------------------
Обратный вызов (callback) представляет собой вызов функции, передаваемой в другую функцию в качестве параметра. Рассмотрим рисунок 19.1. Допустим, в библиотеке описана функция А, параметром которой является имя другой функции. В вызывающем коде описывается функция с требуемой сигнатурой (В) и передается в функцию А. Выполнение функции А приводит к вызову В, то есть управление передается из библиотечной функции обратно в вызывающий код.
Механизм обратного вызова широко используется в программировании. Например, он реализуется во многих стандартных функциях Windows.
В среде Visual Studio 2005, использующей версию 2.0 языка C#, можно применять упрощенный синтаксис для делегатов. Первое упрощение заключается в том, что в большинстве случаев явным образом создавать экземпляр делегата не требуется, поскольку он создается автоматически по контексту. Второе упрощение заключается в возможности создания так называемых анонимных методов — фрагментов кода, описываемых непосредственно в том месте, где используется делегат:
static void Main()
{
Console.WriteLine( " Таблица функции Sin " );
Table( Math.Sin, -2, 2 ); // упрощение 1
Console.WriteLine( " Таблица функции Simple " );
Table( delegate (double x ){ return 1; }, 0, 3 ); // упрощение 2
}
}
}
В первом случае экземпляр делегата, соответствующего функции Sin, создается автоматически. Чтобы это могло произойти, список параметров и тип возвращаемого значения функции должны быть совместимы с делегатом. Во втором случае не требуется оформлять простой фрагмент кода в виде отдельной функции Simple, как это было сделано в предыдущем листинге, — код функции оформляется как анонимный метод и встраивается прямо в место передачи.
Лекция 18. События
Событие — это элемент класса, позволяющий ему посылать другим объектам уведомления об изменении своего состояния. При этом для объектов, являющихся наблюдателями события, активизируются методы-обработчики этого события. Обработчики должны быть зарегистрированы в объекте-источнике события. Таким образом, механизм событий формализует на языковом уровне паттерн «наблюдатель», который рассматривался в предыдущем разделе.
Механизм событий можно также описать с помощью модели «публикация — подписка»: один класс, являющийся отправителем (sender) сообщения, публикует события, которые он может инициировать, а другие классы, являющиеся получателями (receivers) сообщения, подписываются на получение этих событий.
События построены на основе делегатов: с помощью делегатов вызываются методы-обработчики событий. Поэтому создание события в классе состоит из следующих частей:
описание делегата, задающего сигнатуру обработчиков событий;
описание события;
описание метода (методов), инициирующих событие.
Синтаксис события похож на синтаксис делегата:
[ атрибуты ] [ спецификаторы ] event тип имя_события
Для событий применяются спецификаторыnew, public, protected, internal, private, static, virtual, sealed, override, abstract и extern. Например, так же как и методы, событие может быть статическим (static), тогда оно связано с классом в целом, или обычным — в этом случае оно связано с экземпляром класса. Тип события — это тип делегата, на котором основано событие.
Пример описания делегата и соответствующего ему события:
public delegate void Del( object o ); // объявление делегата
class A
{
public event Del Oops;
...
}
Обработка событий выполняется в классах-получателях сообщения. Для этого в них описываются методы-обработчики событий, сигнатура которых соответствует типу делегата. Каждый объект (не класс!), желающий получать сообщение, должен зарегистрировать в объекте-отправителе этот метод.
Как видите, это в точности тот же самый механизм, который рассматривался в предыдущем разделе. Единственное отличие состоит в том, что при использовании событий не требуется описывать метод, регистрирующий обработчики, поскольку события поддерживают операции += и -=, добавляющие обработчик в список и удаляющие его из списка.
В листинге 19.4 приведен код из листинга 19.2, переработанный с использованием событий.
Листинг 19.4. Оповещение наблюдателей с помощью событий
using System;
namespace ConsoleApplication1
{
public delegate void Del(); // объявление делегата
class Subj
{
public event Del Oops; // объявление события
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();
}
}
}
Внешний код может работать с событиями единственным образом: добавлять обработчики в список или удалять их, поскольку вне класса могут использоваться только операции += и -=. Тип результата этих операций — void, в отличие от операций сложного присваивания для арифметических типов.
Внутри класса, в котором описано событие, с ним можно обращаться, как с обычным полем, имеющим тип делегата: использовать операции отношения, присваивания и т. д. Значение события по умолчанию — null.
В библиотеке .NET описано огромное количество стандартных делегатов, предназначенных для реализации механизма обработки событий. Большинство этих классов оформлено по одним и тем же правилам:
имя делегата заканчивается суффиксом EventHandler;
делегат получает два параметра: первый параметр задает источник события и имеет тип object; второй параметр задает аргументы события и имеет тип EventArgs или производный от него.
Если обработчикам события требуется специфическая информация о событии, то для этого создают класс, производный от стандартного класса EventArgs, и добавляют в него необходимую информацию. Если делегат не использует такую информацию, можно обойтись стандартным классом делегата System.EventHandler.
Имя обработчика события принято составлять из префикса On и имени события. В листинге 19.5 приведен пример из листинга 19.4, оформленный в соответствии со стандартными соглашениями .NET.
Листинг 19.5. Использование стандартного делегата EventHandler
using System;
namespace ConsoleApplication1
{
class Subj
{
public event EventHandler Oops;
public void CryOops()
{
Console.WriteLine( "OOPS!" );
if ( Oops != null ) Oops( this, null );
}
}
class ObsA
{
public void OnOops( object sender, EventArgs e )
{
Console.WriteLine( "Вижу, что OOPS!" );