Главная страница

программирование. Руководство su P# a n Reference в herbert schildt полное руководство с 0 герберт шилдт


Скачать 3.32 Mb.
НазваниеРуководство su P# a n Reference в herbert schildt полное руководство с 0 герберт шилдт
Анкорпрограммирование
Дата25.01.2022
Размер3.32 Mb.
Формат файлаrtf
Имя файлаc-40-polnoe-rukovodstvo-2011.rtf
ТипРуководство
#341448
страница41 из 97
1   ...   37   38   39   40   41   42   43   44   ...   97

В одиночном лямбда‑выражении часть, находящаяся справа от оператора =>, воздействует на параметр (или ряд параметров), указываемый слева. Возвращаемым результатом вычисления такого выражения является результат выполнения лямбда‑оператора.

Ниже приведена общая форма одиночного лямбда‑выражения, принимающего единственный параметр.

параметр => выражение

Если же требуется указать несколько параметров, то используется следующая форма.

( список_параметров ) => выражение

Таким образом, когда требуется указать два параметра или более, их следует заключить в скобки. Если же выражение не требует параметров, то следует использовать пустые скобки.

Ниже приведен простой пример одиночного лямбда‑выражения.

count‑ => count + 2

В этом выражении count служит параметром, на который воздействует выражение count + 2. В итоге значение параметра count увеличивается на 2. А вот еще один пример одиночного лямбда‑выражения.

n => п % 2 == О

В данн“ом случае выражение возвращает логическое значение true, если числовое значение параметра п оказывается четным, а иначе – логическое значение false.

Лямбда‑выражение применяется в два этапа. Сначала объявляется тип делегата, совместимый с лямбда‑выражением, а затем экземпляр делегата, которому присваивается лямбда‑выражение. После этого лямбда‑выражение вычисляется при обращении к экземпляру делегата. Результатом его вычисления становится возвращаемое значение.

В приведенном ниже примере программы демонстрируется применение двух одиночных лямбда‑выражений. Сначала в этой программе объявляются два типа делегатов. Первый из них, Inc г, принимает аргумент типа int и возвращает результат того же типа. Второй делегат, IsEven, также принимает аргумент типа int, но возвращает результат типа bool. Затем экземплярам этих делегатов присваиваются одиночные лямбда‑выражения. И наконец, лямбда‑выражения вычисляются с помощью соответствующих экземпляров делегатов.

// Применить два одиночных лямбда‑выражения.

11 Объявить делегат, принимающий аргумент типа int и // возвращающий результат типа int.    '

delegate int Incr(int v);

// Объявить делегат, принимающий аргумент типа int и // возвращающий результат типа bool, delegate bool IsEven(int v);

class SimpleLambdaDemo {

static void Main() {

// Создать делегат Incr, ссылающийся на лямбда‑выражение,

// увеличивающее свой параметр на 2.

Incr incr = count => count + 2;

// А теперь использовать лямбда‑выражение incr.

Console.WriteLine("Использование лямбда‑выражения incr: "); int x = ‑10; while(x <= 0)    {

Console.Write(x + " ");

x = incr(x); // увеличить значение x на 2

}

Console.WriteLine ("\n");

// Создать экземпляр делегата IsEven, ссылающийся на лямбда‑выражение,

// возвращающее логическое значение true, если его параметр имеет четное // значение, а иначе – логическое значение false.

IsEven isEven = n => n % 2 == 0;

// А теперь использовать лямбда‑выражение isEven.

Console.WriteLine("Использование лямбда‑выражения isEven: "); for (int i=l; i <= 10; i++)

if(isEven (i)) Console.WriteLine(i + " четное.");

}

}

Вот к какому результату приводит выполнение этой программы.

Использование лямбда‑выражения incr:

10 ‑8 ‑6 ‑4 ‑2 0

Использование лямбда‑выражения isEven:

2 четное.

4 четное.

6 четное.

8 четное.

10 четьюе.

Обратите в данной программе особое внимание на следующие строки объявлений.

Incr incr = count => count +2;

IsEven isEven = n => n % 2 == 0;

В первой строке объявления экземпляру делегата incr присваивается одиночное лямбда‑выражение, возвращающее результат увеличения на 2 значения параметра count. Это выражение может быть присвоено делегату Incr, поскольку оно совместимо с объявлением данного делегата. Аргумент, указываемый при обращении к экземпляру делегата incr, передается параметру count, который и возвращает результат вычисления лямбда‑выражения. Во второй строке объявления делегату isEven присваивается выражение, возвращающее логическое значение true, если передаваемый ему аргумент оказывается четным, а иначе – логическое значение false. Следовательно, это лямбда‑выражение совместимо с объявлением делегата IsEven.

В связи со всем изложенным выше возникает резонный вопрос: каким образом компилятору становится известно о типе данных, используемых в лямбда‑выражении, например, о типе int параметра count в лямбда‑выражении, присваиваемом экземпляру делегата incr? Ответить на этот вопрос можно так: компилятор делает заключение о типе параметра и типе результата вычисления выражения по типу делегата. Следовательно, параметры и возвращаемое значение лямбда‑выражения должны быть совместимы по типу с параметрами и возвращаемым значением делегата.

Несмотря на всю полезность логического заключения о типе данных, в некоторых случаях приходится явно указывать тип параметра лямбда‑выражения. Для этого достаточно ввести конкретное название типа данных. В качестве примера ниже приведен другой способ объявления экземпляра делегата incr.

Incr incr = (int count) => count + 2;

Как видите, count теперь явно объявлен как параметр типа int. Обратите также внимание на использование скобок. Теперь они необходимы. (Скобки могут быть опущены только в том случае, если задается лишь один параметр, а его тип явно не указывается.)

В предыдущем примере в обоих лямбда‑выражениях использовался единственный параметр, но в целом у лямбда‑выражений может быть любое количество параметров, в том числе и нулевое. Если в лямбда‑выражении используется несколько параметров, их необходимо заключить в скобки. Ниже приведен пример использования лямбда‑выражения с целью определить, находится ли значение в заданных пределах.

(low, high, val) => val >= low && val <= high;

А вот как объявляется тип делегата, совместимого с этим лямбда‑выражением.

delegate bool InRange(int lower, int upper, int v);

Следовательно, экземпляр делегата InRange может быть создан следующим образом.

InRange rangeOK = (low, high, val) => val >= low && val <= high;

После этого одиночное лямбда‑выражение может быть выполнено так, как показано ниже.

if(rangeOK(1, 5, 3)) Console.WriteLine(

"Число 3 находится в пределах от 1 до 5.");

И последнее замечание: внешние переменные могут использоваться и захватываться в лямбда‑выражениях таким же образом, как и в анонимных методах.

Блочные лямбда‑выражения

Как упоминалось выше, существуют две разновидности лямбда‑выражений. Первая из них, одиночное лямбда‑выражение, была рассмотрена в предыдущем разделе. Тело такого лямбда‑выражения состоит только из одного выражения. Второй разновидностью является блочное лямбда‑выражение. Для такого лямбда‑выражения характерны расширенные возможности выполнения различных операций, поскольку в его теле допускается указывать несколько операторов. Например, в блочном лямбда‑выражении можно использовать циклы и условные операторы if, объявлять переменные и т.д. Создать блочное лямбда‑выражение нетрудно. Для этого достаточно заключить тело выражения в фигурные скобки. Помимо возможности использовать несколько операторов, в остальном блочное лямбда‑выражение, практически ничем не отличается от только что рассмотренного одиночного лямбда‑выражения.

Ниже приведен пример использования блочного лямбда‑выражения для вычисления и возврата факториала целого значения.

// Продемонстрировать применение блочного лямбда‑выражения, using System;

// Делегат IntOp принимает один аргумент типа int // и возвращает результат типа int. delegate int IntOp(int end);

class StatementLambdaDemo {

static void Main() {

// Блочное лямбда‑выражение возвращает факториал // передаваемого ему значения.

IntOp fact = n => {

int г = 1;

for (int i=l; i <= n; i++) r = i * r; return r;

};

Console.WriteLine("Факториал 3 равен " + fact(3));

Console.WriteLine("Факториал 5 равен " + fact(5));

}

}

При выполнении этого кода получается следующий результат.

Факториал 3 равен 6 Факториал 5 равен 120

В приведенном выше примере обратите внимание на то, что в теле блочного лямбда‑выражения объявляется переменная г, организуется цикл for и используется оператор return. Все эти элементы вполне допустимы в блочном лямбда‑выражении. И в этом отношении оно очень похоже на анонимный метод. Следовательно, многие анонимные методы могут быть преобразованы в блочные лямбда‑выражения при обновлении унаследованного кода. И еще одно замечание: когда в блочном лямбда‑выражении встречается оператор return, он просто обусловливает возврат из лямбда‑выражения, но не возврат из охватывающего метода.

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

// Первый пример применения делегатов, переделанный с // целью использовать блочные лямбда‑выражения.

using System;

// Объявить тип делегата, delegate string StrMod(string s);

class UseStatementLambdas {

static void Main() {

// Создать делегаты, ссылающиеся на лямбда‑ выражения,

// выполняющие различные операции с символьными строками.

// Заменить пробелы дефисами.

StrMod ReplaceSpaces = s => {

Console.WriteLine("Замена пробелов дефисами."); return s.Replace(' '‑');

};

%    //    Удалить    пробелы.

StrMod RemoveSpaces = s => { string temp = ""; int i;

Console.WriteLine("Удаление пробелов."); for(i=0; i < s.Length; i++) if(s[i] != 1    ')    temp    +=    s[i];

return temp;

};

// Обратить строку.

StrMod Reverse = s => {

string temp = ""; int i, j;

Console.WriteLine("Обращение строки."); for(j=0, i=s.Length‑1; i >= 0; i–, j++) temp += s[i];

return temp;

};

string str;

// Обратиться к лямбда‑выражениям с помощью делегатов.

StrMod strOp = ReplaceSpaces;

str = strOp("Это простой тест.");

Console.WriteLine("Результирующая строка: " + str);

Console.WriteLine() ;

strOp = RemoveSpaces;

str = strOp("Это простой тест.");

Console.WriteLine("Результирующая строка: " + str);

Console.WriteLine();

strOp = Reverse;

str = strOp("Это простой тест.");

Console.WriteLine("Результирующая строка: " + str);

}

}

Результат выполнения кода этого примера оказывается таким же, как и в первом примере применения делегатов.

Замена пробелов дефисами.

Результирующая строка: Это‑простой‑тест.

Удаление пробелов.

Результирующая строка: Этопростойтест.

Обращение строки.

Результирующая строка:    .тсет    йотсорп    отЭ
События

Еще одним важным средством С#, основывающимся на делегатах, является событие. Событие, по существу, представляет собой автоматическое уведомление о том, что произошло некоторое действие. События действуют по следующему принципу: объект, проявляющий интерес к событию, регистрирует обработчик этого события. Когда же событие происходит, вызываются все зарегистрированные обработчики этого события. Обработчики событий обычно представлены делегатами.

События являются членами класса и объявляются с помощью ключевого слова event. Чаще всего для этой цели используется следующая форма:

event делегат_события имя_события;

где делегат_события обозначает имя делегата, используемого для поддержки события, а ммя_событмя – конкретный объект объявляемого события.

Рассмотрим для начала очень простой пример.

// Очень простой пример, демонстрирующий событие, using System;

// Объявить тип делегата для события, delegate void MyEventHandler();

// Объявить класс, содержащий событие, class MyEvent {

public event MyEventHandler SomeEvent;

// Этот метод вызывается для запуска события, public void OnSomeEvent()    {

if (SomeEvent != null)

SomeEvent();

}

}

class EventDemo {

// Обработчик события, static void Handler ()    {

Console.WriteLine("Произошло событие");

}

static void Main() {

MyEvent evt = new MyEvent ();

// Добавить метод Handler() в список событий, evt.SomeEvent += Handler;

// Запустить событие, evt.OnSomeEvent();

}

}

Вот какой результат получается при выполнении этого кода.

Произошло событие

Несмотря на всю свою простоту, данный пример кода содержит все основные элементы, необходимые для обработки событий. Он начинается с объявления типа делегата для обработчика событий, как показано ниже.

delegate void MyEventHandler();

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

Далее создается класс события MyEvent. В этом классе объявляется событие SomeEvent в следующей строке кода.

public event MyEventHandler SomeEvent;

Обратите внимание на синтаксис этого объявления. Ключевое слово event уведомляет компилятор о том, что объявляется событие.

Кроме того, в классе MyEvent объявляется метод OnSomeEvent () , вызываемый для сигнализации о запуске события. Это означает, что он вызывается, когда происходит событие. В методе OnSomeEvent () вызывается обработчик событий с помощью делегата SomeEvent.

if(SomeEvent != null)

SomeEvent();

Как видите, обработчик вызывается лишь в том случае, если событие SomeEvent не является пустым. А поскольку интерес к событию должен быть зарегистрирован в других частях программы, чтобы получать уведомления о нем, то метод OnSomeEvent () может быть вызван до регистрации любого обработчика события. Но во избежание

вызова по пустой ссылке делегат события должен быть проверен, чтобы убедиться в том, что он не является пустым.

В классе Event Demo создается обработчик событий Handler () . В данном простом примере обработчик событий просто выводит сообщение, но другие обработчики могут выполнять более содержательные функции. Далее в методе Main () создается объект класса события MyEvent, a Handler () регистрируется как обработчик этого события, добавляемый в список.

MyEvent evt = new MyEvent ();

// Добавить метод Handler() в список событий, evt.SomeEvent += Handler;

Обратите внимание на то, что обработчик добавляется в список с помощью оператора +=. События поддерживают только операторы += и ‑=. В данном случае метод Handler () является статическим, но в качестве обработчиков событий могут также служить методы экземпляра.

И наконец, событие запускается, как показано ниже.

// Запустить событие, evt.OnSomeEvent();

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

Пример групповой адресации события

Как и делегаты, события поддерживают групповую адресацию. Это дает возможность нескольким объектам реагировать на уведомление о событии. Ниже приведен пример групповой адресации события.

// Продемонстрировать групповую адресацию события, using System;

// Объявить тип делегата для события, delegate void MyEventHandler() ;

// Объявить делегат, содержащий событие, class MyEvent {

public event MyEventHandler SomeEvent;

// Этот метод вызывается для запуска события, public void OnSomeEvent()    {

if(SomeEvent != null)

SomeEvent();

}

}

class X {

public void XhandlerO {

Console.WriteLine("Событие получено объектом класса X");

}

class Y {

public void YhandlerO {

Console.WriteLine("Событие получено объектом класса Y");

}

}

class EventDemo2 {

static void Handler()    {

Console.WriteLine("Событие получено объектом класса EventDemo");

}

static void Main() {

MyEvent evt = new MyEvent ();

X xOb = new X();

Y yOb = new Y();

// Добавить обработчики в список событий.

evt.SomeEvent += Handler;

evt.SomeEvent += xOb.Xhandler;

evt.SomeEvent += yOb.Yhandler;

// Запустить событие, evt.OnSomeEvent() ;

Console.WriteLine() ;

// Удалить обработчик.

evt.SomeEvent ‑= xOb.Xhandler;

evt.OnSomeEvent() ;

}

}

При выполнении кода этого примера получается следующий результат.

Событие получено объектом класса EventDemo Событие получено объектом класса X Событие получено объектом класса Y

Событие получено объектом класса EventDemo Событие получено объектом класса Y

В данном примере создаются два дополнительных класса, X и Y, в которых также определяются обработчики событий, совместимые с делегатом MyEventHandler. Поэтому эти обработчики могут быть также включены в цепочку событий. Обратите внимание на то, что обработчики в классах X и Y не являются статическими. Это означает, что сначала должны быть созданы объекты каждого из этих классов, а затем в цепочку событий должны быть введены обработчики, связанные с их экземплярами. Об отличиях между обработчиками экземпляра и статическими обработчиками речь пойдет в следующем разделе.

Методы экземпляра в сравнении со статическими методами в качестве обработчиков событий

Методы экземпляра и статические методы могут быть использованы в качестве обработчиков событий, но между ними имеется одно существенное отличие. Когда

статический метод используется в качестве обработчика, уведомление о событии распространяется на весь класс. А когда в качестве обработчика используется метод экземпляра, то события адресуются конкретным экземплярам объектов. Следовательно, каждый объект определенного класса, которому требуется получить уведомление о событии, должен быть зарегистрирован отдельно. На практике большинство обработчиков событий представляет собой методы экземпляра, хотя это, конечно, зависит от конкретного приложения. Рассмотрим применение каждой из этих двух разновидностей методов в качестве обработчиков событий на конкретных примерах.

В приведенной ниже программе создается класс X, в котором метод экземпляра определяется в качестве обработчика событий. Это означает, что каждый объект класса X должен быть зарегистрирован отдельно, чтобы получать уведомления о событиях. Для демонстрации этого факта в данной программе производится групповая адресация события трем отдельным объектам класса X.

/* Уведомления о событиях получают отдельные объекты, когда метод экземпляра используется в качестве обработчика событий. */

using System;

// Объявить тип делегата для события, delegate void MyEventHandler() ;

// Объявить класс, содержащий событие, class MyEvent {

public event MyEventHandler SomeEvent;

// Этот метод вызывается для запуска события, public void OnSomeEvent()    {

if(SomeEvent != null)

SomeEvent() ;

}

}

class X { int id;

public X(int x) { id = x; }

// Этот метод экземпляра предназначен в качестве обработчика событий, public void Xhandler() {

Console.WriteLine("Событие получено объектом " + id);

}

}

class EventDemo3 { static void Main() {

MyEvent evt = new MyEvent();

X ol = new X(1);

X o2 = new X (2);

X o3 = new X(3); evt.SomeEvent += ol.Xhandler; evt.SomeEvent += o2.Xhandler; evt.SomeEvent += o3.Xhandler;

// Запустить событие, evt.OnSomeEvent() ;

}

}

Выполнение кода из этого примера приводит к следующему результату.

Событие получено объектом 1 Событие получено объектом 2 Событие получено объектом 3

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

С другой стороны, когда в качестве обработчика событий используется статический метод, события обрабатываются независимо от какого‑либо объекта, как демонстрируется в приведенном ниже примере программы.

/* Уведомления о событии получает класс, когда статический метод используется в качестве обработчика событий. */

using System;

// Объявить тип делегата для события, delegate void MyEventHandler();

// Объявить класс, содержащий событие, class MyEvent {

public event MyEventHandler SomeEvent;

// Этот метод вызывается для запуска события, public void OnSomeEvent()    {

if (SomeEvent != null)

SomeEvent() ;

}

}

class X {

/* Этот статический метод предназначен в качестве обработчика событий. */ public static void Xhandler() {

Console.WriteLine("Событие получено классом.");

}

}

class EventDemo4 { static void Main() {

MyEvent evt = new MyEvent();

evt.SomeEvent += X.Xhandler;

// Запустить событие, evt.OnSomeEvent();

При выполнение кода этого примера получается следующий результат.

Событие получено классом.

Обратите в данном примере внимание на то, что объекты класса X вообще не создаются. Но поскольку Xhandler () является статическим методом класса X, то он может быть привязан к событию SomeEvent и выполнен при вызове метода

OnSomeEvent().

Применение аксессоров событий

В приведенных выше примерах события формировались в форме, допускавшей автоматическое управление списком вызовов обработчиков событий, включая добавление и удаление обработчиков событий из списка. Поэтому управление этим списком не нужно было организовывать вручную. Благодаря именно этому свойству такие события используются чаще всего. Тем не менее организовать управление списком вызовов обработчиков событий можно и вручную, чтобы, например, реализовать специальный механизм сохранения событий.

Для управления списком обработчиков событий служит расширенная форма оператора event, позволяющая использовать аксессоры событий. Эти аксессоры предоставляют средства для управления реализацией подобного списка в приведенной ниже форме.

event делегат_события имя_ с о бытия { add {

// Код добавления события в цепочку событий.

}

remove {

// Код удаления события из цепочки событий.

}

}

В эту форму входят два аксессора событий: add и remove. Аксессор add вызывается, когда обработчик событий добавляется в цепочку событий с помощью оператора +=. В то же время аксессор remove вызывается, когда обработчик событий удаляется из цепочки событий с помощью оператора ‑=.

Когда вызывается аксессор add или remove, он принимает в качестве параметра добавляемый или удаляемый обработчик. Как и в других разновидностях аксессоров, этот неявный параметр называется value. Реализовав аксессоры add или remove, можно организовать специальную схему хранения обработчиков событий. Например, обработчики событий можно хранить в массиве, стеке или очереди.

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

// Создать специальные средства для управления списками // вызова обработчиков событий.

using System;

// Объявить тип делегата для события.

delegate void MyEventHandler();    ,

// Объявить класс для хранения максимум трех событий, class MyEvent {

MyEventHandler[] evnt = new MyEventHandler[3];

public event MyEventHandler SomeEvent {

// Добавить событие в список, add { int i;

for(i=0; i < 3; i++) if(evnt[i] == null) { evnt[i] = value; break;

}

if (i == 3) Console.WriteLine("Список событий заполнен.");

}

// Удалить событие из списка, remove { int i;

for(i=0; i < 3; i++) if(evnt[i] == value) { evnt[i] = null; break;

}

if (i == 3) Console.WriteLine("Обработчик событий не найден.");

}

}

// Этот .метод вызывается для запуска событий, public void OnSomeEvent()    {

for(int i=0; i < 3; i++)

if(evnt[i] != null) evnt[i]();

}

}

// Создать ряд классов, использующих делегат MyEventHandler. class W {

public void Whandler() {

Console.WriteLine("Событие получено объектом W");

}

}

class X {

public void Xhandler() {

Console.WriteLine("Событие получено объектом X");

}

}

class Y {

public void Yhandler() {

Console.WriteLine("Событие получено объектом Y");

class Z {

public void Zhandler() {

Console.WriteLine("Событие получено объектом Z");

}

}

class EventDemo5 { static void Main() {

MyEvent evt = new MyEvent();

// Добавить обработчики в цепочку событий.

Console.WriteLine("Добавление событий."); evt.SomeEvent += wOb.Whandler; evt.SomeEvent += xOb.Xhandler; evt.SomeEvent += yOb.Yhandler;

// Сохранить нельзя ‑ список заполнен, evt.SomeEvent += zOb.Zhandler;

Console.WriteLine();

// Запустить события, evt.OnSomeEvent();

Console.WriteLine();

// Удалить обработчик.

Console.WriteLine("Удаление обработчика xOb.Xhandler.") ; evt.SomeEvent ‑= xOb.Xhandler; evt.OnSomeEvent();

Console.WriteLine();

// Попробовать удалить обработчик еще раз.

Console.WriteLine("Попытка удалить обработчик " +

"xOb.Xhandler еще раз."); evt.SomeEvent ‑= xOb.Xhandler; evt.OnSomeEvent();

Console.WriteLine();

//А теперь добавить обработчик Zhandler.

Console.WriteLine("Добавление обработчика zOb.Zhandler."); evt.SomeEvent += zOb.Zhandler; evt.OnSomeEvent();

}

}

Добавление событий.

Список событий заполнен.

Событие получено объектом W Событие получено объектом X Событие получено объектом Y

Удаление обработчика xOb.Xhandler.

Событие получено объектом W Событие получено объектом Y

Попытка удалить обработчик xOb.Xhandler еще раз.

Обработчик событий не найден.

Событие получено объектом W Событие получено объектом Y

Добавление обработчика zOb.Zhandler.

Событие получено объектом W Событие получено объектом X Событие получено объектом Y

Рассмотрим данную программу более подробно. Сначала в ней определяется делегат обработчиков событий MyEventHandler. Затем объявляется класс MyEvent. В самом его начале определяется массив обработчиков событий evnt, состоящий из трех элементов.

MyEventHandler[] evnt = new MyEventHandler[3];

Этот массив служит для хранения обработчиков событий, добавляемых в цепочку событий. По умолчанию элементы массива evnt инициализируются пустым значением (null).

Далее объявляется событие SomeEvent. В этом объявлении используется приведенная ниже аксессорная форма оператора event.

public event MyEventHandler SomeEvent {

// Добавить событие в список, add { int i;

for(i=0; i < 3; i++) if(evnt[i] == null) { evnt[i] = value; break;

}

if (i == 3) Console.WriteLine("Список событий заполнен.");

}

// Удалить событие из списка, remove { int i; for(i=0; i < 3; i++) if(evnt[i] == value) { evnt[i] = null; break;

}

Когда в цепочку событий добавляется обработчик событий, вызывается аксессор add, и в первом неиспользуемом (т.е. пустом) элементе массива evnt запоминается ссылка на этот обработчик, содержащаяся в неявно задаваемом параметре value. Если в массиве отсутствуют свободные элементы, то выдается сообщение об ошибке. (Разумеется, в реальном коде при переполнении списка лучше сгенерировать соответствующее исключение.) Массив evnt состоит всего из трех элементов, поэтому в нем можно сохранить только три обработчика событий. Когда же обработчик событий удаляется из цепочки событий, то вызывается аксессор remove и в массиве evnt осуществляется поиск ссылки на этот обработчик, передаваемой в качестве параметра value. Если ссылка найдена, то соответствующему элементу массива присваивается пустое значение (null), а значит, обработчик удаляется из цепочки событий.

При запуске события вызывается метод OnSomeEvent (). В этом методе происходит циклическое обращение к элементам массива evnt для вызова по очереди каждого обработчика событий.

Как демонстрирует рассматриваемый здесь пример программы, механизм хранения обработчиков событий нетрудно реализовать, если в этом есть потребность. Но для большинства приложений более подходящим оказывается используемый по умолчанию механизм хранения обработчиков событий, который обеспечивает форма оператора event без аксессоров. Тем не менее аксессорная форма оператора event используется в особых случаях. Так, если обработчики событий необходимо выполнять в программе в порядке их приоритетности, а не в том порядке, в каком они вводятся в цепочку событий, то для их хранения можно воспользоваться очередью по приоритету.

ПРИМЕЧАНИЕ

В многопоточных приложениях обычно приходится синхронизировать доступ к аксессо‑рам событий. Подробнее о многопоточном программировании речь пойдет в главе’23.

Разнообразные возможности событий

События могут быть определены и в интерфейсах. При этом события должны предоставляться классами, реализующими интерфейсы. События могут быть также определены как абстрактные (abstract). В этом случае конкретное событие должно быть реализовано в производном классе. Но аксессорные формы событий не могут быть абстрактными. Кроме того, событие может быть определено как герметичное (sealed). И наконец, событие может быть виртуальным, т.е. его можно переопределить в производном классе.

Применение анонимных методов и лямбда‑выражений вместе с событиями

Анонимные методы и лямбда‑выражения особенно удобны для работы с событиями, поскольку обработчик событий зачастую вызывается только в коде, реализующем механизм обработки событий. Это означает, что создавать автономный метод, как правило, нет никаких причин. А с помощью лямбда‑выражений или анонимных методов можно существенно упростить код обработки событий.

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

// Использовать лямбда‑выражение в качестве обработчика событий, using System;

// Объявить тип делегата для события, delegate void MyEventHandler(int n);

// Объявить класс, содержащий событие, class MyEvent {

public event MyEventHandler SomeEvent;

// Этот метод вызывается для запуска события, public void OnSomeEvent(int n) { if(SomeEvent != null)

SomeEvent(n);

}

}

class LambdaEventDemo { static void Main() {

MyEvent evt = new MyEvent();

// Использовать лямбда‑выражение в качестве обработчика событий, evt.SomeEvent += (n) =>

Console.WriteLine("Событие получено. Значение равно " + п);

// Запустить событие, evt.OnSomeEvent(1); evt.OnSomeEvent(2);

}

}

Вот к какому результату приводит выполнение этой программы.

Событие получено. Значение равно 1 Событие получено. Значение равно 2

Обратите особое внимание на то, как в этой программе лямбда‑выражение используется в качестве обработчика событий.

evt.SomeEvent += (n) =>

Console.WriteLine("Событие получено. Значение равно " + п);

Синтаксис для использования лямбда‑выражения в качестве обработчика событий остается таким же, как для его применения вместе с любым другим типом делегата.

Несмотря на то что при создании анонимной функции предпочтение следует теперь отдавать лямбда‑выражениям, в качестве обработчика событий можно по‑прежнему использовать анонимный метод. Ниже приведен вариант обработчика событий из предыдущего примера, измененный с целью продемонстрировать применение анонимного метода.

11 Использовать анонимный метод в качестве обработчика событий, evt.SomeEvent += delegate(int n) {

Console.WriteLine("Событие получено. Значение равно " + n);

};

Как видите, синтаксис использования анонимного метода в качестве обработчика событий остается таким же, как и для его применения вместе с любым другим типом делегата.

Рекомендации по обработке событий в среде .NET Framework

В C# разрешается формировать какие угодно разновидности событий. Но ради совместимости программных компонентов со средой .NET Framework следует придерживаться рекомендаций, установленных для этой цели корпорацией Microsoft. Эти рекомендации, по существу, сводятся к следующему требованию: у обработчиков событий должны быть два параметра. Первый из них – ссылка на объект, формирующий событие, второй – параметр типа EventArgs, содержащий любую дополнительную информацию о событии, которая требуется обработчику. Таким образом, .NET‑совместимые обработчики событий должны иметь следующую общую форму.

void обработчик (object отправитель , EventArgs е) {

// ...

}

Как правило, отправитель – это параметр, передаваемый вызывающим кодом с помощью ключевого слова this. А параметр е типа EventArgs содержит дополнительную информацию о событии и может быть проигнорирован, если он не нужен.

Сам класс EventArgs не содержит поля, которые могут быть использованы для передачи дополнительных данных обработчику. Напротив, EventArgs служит в качестве базового класса, от которого получается производный класс, содержащий все необходимые поля. Тем не менее в классе EventArgs имеется одно поле Empty типа static, которое представляет собой объект типа EventArgs без данных.

Ниже приведен пример программы, в которой формируется .NET‑совместимое событие.

// Пример формирования .NET‑совместимого события, using System;

// Объявить класс, производный от класса EventArgs. class MyEventArgs : EventArgs { public int EventNum;

}

// Объявить тип делегата для события.

delegate void MyEventHandler(object source, MyEventArgs arg);

/’/ Объявить класс, содержащий событие, class MyEvent {

static int count = 0;

// Этот метод запускает событие SomeEvent. public void OnSomeEvent()    {

MyEventArgs arg = new MyEventArgs();

if(SomeEvent != null) { arg.EventNum = count++;

SomeEvent(this, arg);

}

}

}

class X {

public void Handler(object source, MyEventArgs arg) { Console.WriteLine("Событие " + arg.EventNum +

" получено объектом класса X."); Console.WriteLine("Источник: " + source);

Console.WriteLine();

}

}

class Y {

public void Handler(object source, MyEventArgs arg) { Console.WriteLine("Событие " + arg.EventNum +

" получено объектом класса Y."); Console.WriteLine("Источник: " + source);

Console.WriteLine() ;

}

}

class EventDemo6 { static void Main() {

X obi = new X ();

Y ob2 = new Y ();

MyEvent evt ‑ new* MyEvent ();

// Добавить обработчик Handler() в цепочку событий, evt ..SomeEvent += obi. Handler; evt.SomeEvent += ob2.Handler;

// Запустить событие, evt.OnSomeEvent(); evt.OnSomeEvent();

}

}

Ниже приведен результат выполнения этой программы.

Событие 0 получено объектом класса X Источник: MyEvent

Событие 0 получено объектом класса Y Источник: MyEvent

Событие 1 получено объектом класса X Источник: MyEvent

Событие 1 получено объектом класса Y Источник: MyEvent

В данном примере создается класс MyEventArgs, производный от класса EventArgs. В классе MyEventArgs добавляется лишь одно его собственное поле: EventNum. Затем объявляется делегат MyEventHandler, принимающий два параметра, требующиеся для среды .NET Framework. Как пояснялось выше, первый параметр содержит ссылку на объект, формирующий событие, а второй параметр – ссылку на объект класса EventArgs или производного от него класса. Обработчики событий Handler () , определяемые в классах X и Y, принимают параметры тех же самых типов.

В классе MyEvent объявляется событие SomeEvent типа MyEventHandler. Это событие запускается в методе OnSomeEvent () с помощью делегата SomeEvent, которому в качестве первого аргумента передается ссылка this, а вторым аргументом служит экземпляр объекта типа MyEventArgs. Таким образом, делегату типа MyEventHandler передаются надлежащие аргументы в соответствии с требованиями совместимости со средой .NET.

Применение делегатов EventHandler и EventHandler

В приведенном выше примере программы объявлялся собственный делегат события. Но как правило, в этом не никакой необходимости, поскольку в среде .NET Framework предоставляется встроенный обобщенный делегат под названием EventHandler. (Более подробно обобщенные типы рассматриваются в главе 18.) В данном случае тип TEventArgs обозначает тип аргумента, передаваемого параметру EventArgs события. Например, в приведенной выше программе событие SomeEvent может быть объявлено в классе MyEvent следующим образом.

public event EventHandler SomeEvent;

В общем, рекомендуется пользоваться именно таким способом, а не определять собственный делегат.

Для обработки многих событий параметр типа EventArgs оказывается ненужным. Поэтому с целью упростить создание кода в подобных ситуациях в среду .NET Framework внедрен необобщенный делегат типа EventHandler. Он может быть использован для объявления обработчиков событий, которым не требуется дополнительная информация о событиях. Ниже приведен пример использования делегата EventHandler.

// Использовать встроенный делегат EventHandler. using System;

// Объявить класс, содержащий событие, class MyEvent {

public event EventHandler SomeEvent; // использовать делегат EventHandler

// Этот метод вызывается для запуска события.

public void OnSomeEvent()    {

if(SomeEvent != null)

SomeEvent(this, EventArgs.Empty);

}

}

class EventDemo7 {

static void handler(object source, EventArgs arg) {

Console.WriteLine("Произошло событие");

Console.WriteLine("Источник: " + source);

}

static void Main() {

MyEvent evt = new MyEvent();

// Добавить обработчик Handler() в цепочку событий, evt.SomeEvent += Handler;

// Запустить событие, evt.OnSomeEvent() ;

}

}

В данном примере параметр типа EventArgs не используется, поэтому в качестве этого параметра передается объект‑заполнитель EventArgs . Empty. Результат выполнения кода из данного примера следующий.

Произошло событие Источник: MyEvent

Практический пример обработки событий

События нередко применяются в таких ориентированных на обмен сообщениями средах, как Windows. В подобной среде программа просто ожидает до тех пор, пока не будет получено конкретное сообщение, а затем она предпринимает соответствующее действие. Такая архитектура вполне пригодна для обработки событий средствами С#, поскольку дает возможность создавать обработчики событий для реагирования на различные сообщения и затем просто вызывать обработчик при получении конкретного сообщения. Так, щелчок левой кнопкой мыши может быть связан с событием LButtonClick. При получении сообщения о щелчке левой кнопкой мыши вызывается метод OnLButtonClick () , и об этом событии уведомляются все зарегистрированные обработчики.

Разработка программ для Windows, демонстрирующих такой подход, выходит за рамки этой главы, тем не менее, рассмотрим пример, дающий представление о принципе, по которому действует данный подход. В приведенной ниже программе создается обработчик событий, связанных с нажатием клавиш. Всякий раз, когда на клавиатуре нажимается клавиша, запускается событие KeyPress при вызове метода OnKeyPress () . Следует заметить, что в этой программе формируются .NET‑совместимые события и что их обработчики предоставляются в лямбда‑выражениях.

// Пример обработки событий, связанных с нажатием клавиш на клавиатуре, using System;

// Создать класс, производный от класса EventArgs и // хранящий символ нажатой клавиши.

class KeyEventArgs : EventArgs { public char ch;

}

// Объявить класс события, связанного с нажатием клавиш на клавиатуре, class KeyEvent {

public event EventHandler KeyPress;

// Этот метод вызывается при нажатии клавиши, public void OnKeyPress(char key) {

KeyEventArgs k = new KeyEventArgs();

if(KeyPress != null) { k.ch = key;

KeyPress(this, k) ;

}

}

}

// Продемонстрировать обработку события типа KeyEvent. class KeyEventDemo { static void Main() {

KeyEvent kevt = new KeyEvent();

ConsoleKeylnfo key; int count = 0;

// Использовать лямбда‑выражение для отображения факта нажатия клавиши, kevt.KeyPress += (sender, е) =>

Console.WriteLine(" Получено сообщение о нажатии клавиши: " + e.ch);

// Использовать лямбда‑выражение для подсчета нажатых клавиш.

kevt.KeyPress += (sender, е) =>

count++; // count – это внешняя переменная

Console.WriteLine("Введите несколько символов. " +

"По завершении введите точку.");

do {

key = Console.ReadKey(); kevt.OnKeyPress(key.KeyChar);

} while(key.KeyChar != '.');

Console.WriteLine("Было нажато " + count + " клавиш.");

}

}

Вот, например, к какому результату приводит выполнение этой программы.

Было нажато 5 клавиш.

В самом начале этой программы объявляется класс KeyEventArgs, производный от класса EventArgs и служащий для передачи сообщения о нажатии клавиши обработчику событий. Затем объявляется обобщенный делегат EventHandler, определяющий обработчик событий, связанных с нажатием клавиш. Эти события инкапсулируются в классе KeyEvent, где определяется событие KeyPress.

В методе Main () сначала создается объект kevt класса KeyEvent. Затем в цепочку событий kevt. KeyPress добавляется обработчик, предоставляемый лямбда‑выражением. В этом обработчике отображается факт каждого нажатия клавиши, как показано ниже.

kevt.KeyPress += (sender, е) =>

Console.WriteLine(" Получено сообщение о нажатии клавиши: " + e.ch);

Далее в цепочку событий kevt .KeyPress добавляется еще один обработчик, предоставляемый лямбда‑выражением. В этом обработчике подсчитывается количество нажатых клавиш, как показано ниже.

kevt.KeyPress += (sender, е) =>

count++; // count – это внешняя переменная

Обратите внимание на то, что count является локальной переменной, объявленной в методе Main () и инициализированной нулевым значением.

Далее начинает выполняться цикл, в котором метод kevt. OnKeyPress () вызывается при нажатии клавиши. Об этом событии уведомляются все зарегистрированные обработчики событий. По окончании цикла отображается количество нажатых клавиш. Несмотря на всю свою простоту, данный пример наглядно демонстрирует саму суть обработки событий средствами С#. Аналогичный подход может быть использован и для обработки других событий. Безусловно, в некоторых случаях анонимные обработчики событий могут оказаться непригодными, и тогда придется внедрить именованные методы.

1   ...   37   38   39   40   41   42   43   44   ...   97


написать администратору сайта