Примечание

Событие — это удобная абстракция для программиста. На самом деле оно состоит из закрытого статического класса, в котором создается экземпляр делегата, и двух методов, предназначенных для добавления и удаления обработчика из списка этого делегата.

В листинге 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. Многопоточные приложения создают как для многопроцессорных, так и для од­нопроцессорных систем. Основной целью при этом являются повышение общей производительности и сокращение времени реакции приложения. Управление потоками осуществляет операционная система. Каждый поток получает некото­рое количество квантов времени, по истечении которого управление передается другому потоку. Это создает у пользователя однопроцессорной машины впечат­ление одновременной работы нескольких потоков и позволяет, к примеру, вы­полнять ввод текста одновременно с длительной операцией по передаче данных. Недостатки многопоточности:

□ большое количество потоков ведет к увеличению накладных расходов, связанных с их переключением, что снижает общую производительность системы;

□ в многопоточных приложениях возникают проблемы синхронизации данных,
связанные с потенциальной возможностью доступа к одним и тем же данным
со стороны нескольких потоков (например, если один поток начинает изменение общих данных, а отведенное ему время истекает, доступ к этим же данным может получить другой поток, который, изменяя данные, необратимо их
повреждает).


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: