Событие — это удобная абстракция для программиста. На самом деле оно состоит из закрытого статического класса, в котором создается экземпляр делегата, и двух методов, предназначенных для добавления и удаления обработчика из списка этого делегата.
В листинге 10.7 приведен код из листинга 10.2, переработанный с использованием событий.
Приводится упрощенный вариант. В общем случае имеется возможность задавать несколько имен событий, инициализаторы и методы добавления и удаления событий.
Листинг 10.7. Оповещение наблюдателей с помощью событий
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 ol = new ObsA(); // объекты
ObsA o2 = new ObsA(); // класса-наблюдателя
s.Oops += new Del(ol.Do); // добавление
s.Oops += new Del(o2.Do); // обработчиков
s.Oops += new Del(ObsB.See); // к событию
s.CryOops(); // инициирование события
}
}
}
Внешний код может работать с событиями единственным образом: добавлять обработчики в список или удалять их, поскольку вне класса могут использоваться только операции += и -=. Тип результата этих операций — void, в отличие от операций сложного присваивания для арифметических типов. Иного способа доступа к списку обработчиков нет.
Внутри класса, в котором описано событие, с ним можно обращаться, как с обычным полем, имеющим тип делегата: использовать операции отношения, присваивания и т. д. Значение события по умолчанию — null. Например, в методе CryOops выполняется проверка на null для того, чтобы избежать генерации исключения System.NullReferenceException.
В библиотеке.NET описано огромное количество стандартных делегатов, предназначенных для реализации механизма обработки событий. Большинство этих классов оформлено по одним и тем же правилам:
□ имя делегата заканчивается суффиксом EventHandler;
□ делегат получает два параметра:
О первый параметр задает источник события и имеет тип object;
О второй параметр задает аргументы события и имеет тип EventArgs или производный от него.
Если обработчикам события требуется специфическая информация о событии, то для этого создают класс, производный от стандартного класса EventArgs, и добавляют в него необходимую информацию. Если делегат не использует такую информацию, можно не описывать делегата и собственный тип аргументов, а обойтись стандартным классом делегата System.EventHandler.
|
|
Имя обработчика события принято составлять из префикса On и имени события. В листинге 10.8 приведен пример из листинга 10.7, оформленный в соответствии со стандартными соглашениями.NET. Найдите восемь отличий!
Листинг 10.8. Использование стандартного делегата 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!");
}
}
class ObsB
{
public static void OnOops(object sender, EventArgs e)
{
Console.WriteLine("Я тоже вижу, что OOPS!");
}
}
class Class1
{
static void Main()
{
Subj s = new Subj();
ObsA o1 = new ObsA();
ObsA o2 = new ObsA();
s.Oops += new EventHandler(o1.OnOops);
s.Oops += new EventHandler(o2.OnOops);
s.Oops += new EventHandler(ObsB.OnOops);
s.CryOops();
}
}
}
}
Те, кто работает с С# версии 2.0, могут упростить эту программу, используя новую возможность неявного создания делегатов при регистрации обработчиков событий. Соответствующий вариант приведен в листинге 10.9. В демонстрационных целях в код добавлен новый анонимный обработчик — еще один механизм, появившийся в новой версии языка.
Листинг 10.9. Использование делегатов и анонимных методов (версия 2.0)
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!");
}
}
class ObsB
{
public static void OnOops(object sender, EventArgs e)
{
Console.WriteLine("Я тоже вижу, что OOPS!");
}
}
class Class1
{
static void Main()
{
Subj s = new Subj();
ObsA o1 = new ObsA(); ObsA o2 = new ObsA();
s.Oops += ol.OnOops; s.Oops += o2.OnOops; s.Oops += ObsB.OnOops;
s.Oops += delegate(object sender, EventArgs e)
{
Console.WriteLine("Я с вами!");
};
s.CryOops();
}
}
}
События включены во многие стандартные классы.NET, например, в классы пространства имен Windows.Forms, используемые для разработки Windows-приложений. Мы рассмотрим эти классы в главе 14.
Многопоточные приложения
Приложение.NET состоит из одного или нескольких процессов. Процессу принадлежат выделенная для него область оперативной памяти и ресурсы. Каждый Процесс может состоять из нескольких доменов (частей) приложения, ресурсы которых изолированы друг от друга. В рамках домена может быть запущено несколько потоков выполнения. Поток (thread1)
1 Иногда этот термин переводится буквально — «нить», чтобы отличить его от потоков ввода-вывода, которые рассматриваются в следующей главе. Поэтому в литературе можно встретить и термин «многонитевые приложения».
представляет собой часть исполняемого кода программы. В каждом процессе есть первичный поток, исполняющий роль точки входа в приложение. Для консольных приложений это метод Main. Многопоточные приложения создают как для многопроцессорных, так и для однопроцессорных систем. Основной целью при этом являются повышение общей производительности и сокращение времени реакции приложения. Управление потоками осуществляет операционная система. Каждый поток получает некоторое количество квантов времени, по истечении которого управление передается другому потоку. Это создает у пользователя однопроцессорной машины впечатление одновременной работы нескольких потоков и позволяет, к примеру, выполнять ввод текста одновременно с длительной операцией по передаче данных. Недостатки многопоточности:
□ большое количество потоков ведет к увеличению накладных расходов, связанных с их переключением, что снижает общую производительность системы;
□ в многопоточных приложениях возникают проблемы синхронизации данных,
связанные с потенциальной возможностью доступа к одним и тем же данным
со стороны нескольких потоков (например, если один поток начинает изменение общих данных, а отведенное ему время истекает, доступ к этим же данным может получить другой поток, который, изменяя данные, необратимо их
повреждает).
|
|