Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции по ООП (язык C#).pdf
Скачиваний:
190
Добавлен:
16.05.2015
Размер:
1.54 Mб
Скачать

Чернов Э. А.

- 120 -

Лекции по языку C# v 2.3

List<Block> lst = new List<Block>() ;

Формат описания вызова метода Find имеет вид:

Block List<Block>. Find(Predicate <Block> match)

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

Searches for an element, that matches the conditions, defined by specified predicate, and return the first occurrence within the entire System.Collections.Generic.List<T>

match: The System.Predicate<T> delegate that defines the conditions of the element to search for.

(Разыскивает элемент, который удовлетворяет условиям, заданным предикатом, и возвращает первое появление во всем списке

Sytem.Collections.Generic.List<T>.

match: (обобщенный) делегат System.Predicate<T>, который задает условия для разыскиваемого элемента).

Ниже показана строка оформленного лямда-выражения для поиска:

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

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

Обработка событий

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

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

События имеют следующие свойства (применяемые в msdn термины).

Чернов Э. А.

- 121 -

Лекции по языку C# v 2.3

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

У события может быть несколько подписчиков. Подписчик может обрабатывать несколько событий от нескольких издателей.

События, не имеющие подписчиков, никогда не возникают.

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

Если событие имеет несколько подписчиков, то при его возникновении происходит синхронный вызов обработчиков событий. Сведения об асинхронном вызове событий см. в разделе HАсинхронный вызов синхронных методовH.

В библиотеке классов .NET Framework в основе событий лежит делегат

HEventHandlerH и базовый класс HEventArgsH.

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

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

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

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

//Объявить тип делегата для события, 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();

Console.ReadKey();

Чернов Э. А.

- 122 -

Лекции по языку C# v 2.3

}

}

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

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

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

delegate void MyEventHandler();

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

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

public event MyEventHandler SomeEvent;

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

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

if(SomeEvent != null) SomeEvent();

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

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

MyEvent evt = new MyEvent();

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

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

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

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

Чернов Э. А.

- 123 -

Лекции по языку C# v 2.3

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

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

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

//Создать класс, производный от класса EventArgs и

//хранящий символ нажатой клавиши.

class KeyEventArgs: EventArgs

{

public char ch;

}

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

{

public event EventHandler<KeyEventArgs> 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();

ConsoleKeyInfo key; int count = 0;

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

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

//Использовать лямбда-выражение для подсчета нажатых клавиш. kevt.KeyPress += (sender, e) => count++; // count - это внешняя переменная

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

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

do

{

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

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

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

Чернов Э. А.

- 124 -

Лекции по языку C# v 2.3

}

}

В среде .NET Framework предоставляется встроенный обобщенный делегат под названием EventHandler <TEventArgs>. Этот делегат связывает событие с его обработчиком. Для вызова события требуются два элемента:

делегат, который ссылается на метод, вызываемый в ответ на событие;

Дополнительно класс, который содержит данные события, если событие содержит данные. (msdn)

Объявление делегата имеет форму:

[SerializableAttribute]

public delegate void EventHandler<TEventArgs>(Object sender, TEventArgs e)

Параметры типа

TEventArgs - Тип данных, создаваемых событием.

Параметры

sender - Тип: System.Object - источник события.

e - Тип: TEventArgs - объект, содержащий данные о событии.

Метод, определяемый делегатом, имеет тип возврата void. Первый параметр типа Object связан с объектом, порождающим событие. Второй параметр, производный от типа TEventArgsT, содержит информацию о событии и передает все поля, необходимые для обработки события (нажатую клавишу, информацию о кнопке мыши (правая, левая) и т. д.).

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

private void button1_MouseClick(object sender, MouseEventArgs e)

{

if (e.Button == MouseButtons.Right)

{

textBox1.Text = "Вы щелкнули правой кнопкой";

}

}

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

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

Чернов Э. А.

- 125 -

Лекции по языку C# v 2.3

делегатом, определяется во время выполнения, а не в процессе компиляции. В этом, собственно, и заключается главное преимущество делегата. ПРИМЕЧАНИЕ. Если у вас имеется опыт программирования на C/C++, то вам по-

лезно будет знать, что делегат в С# подобен указателю на функцию в

C/C++.

Тип делегата объявляется с помощью ключевого слова delegate. Ниже приведена общая форма объявления делегата:

delegate возвращаемый_тип имя(список_параметров) ;

где возвращаемый_тип обозначает тип значения, возвращаемого методами, которые будут вызываться делегатом; имя - конкретное имя делегата; список_параметров - параметры, необходимые для

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

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

namespace DelegateTest

{

// Объявить тип делегата. delegate string StrMod(string str) ; class DelegateTest

{

//Заменить пробелы дефисами. static string ReplaceSpaces(string s)

{

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

}

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

static string RemoveSpaces(string s)

{

string temp = ""; int i;

Console.WriteLine("Удаление пробелов."); for(i=0; i < s.Length; i++)

if(s[i] != ' ') temp += s[i]; return temp;

}

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

static string Reverse(string s)

{

string temp = ""; int i, j;

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

temp += s[i]; return temp;

}

static void Main(string[] args)

{

// Сконструировать делегат.

Чернов Э. А.

- 126 -

Лекции по языку C# v 2.3

StrMod strOp = new StrMod(ReplaceSpaces); string str;

// Вызвать методы с помощью делегата. str = strOp("Это простой тест.");

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

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

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

Console.WriteLine();

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

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

//return ;

}

}

}

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

Замена пробелов дефисами. Результирующая строка: Это-простой-тест.

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

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

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

Рассмотрим данный пример более подробно. В его коде сначала объявляется делегат StrMod типа string, как показано ниже.

delegate string StrMod(string str);

Как видите, делегат StrMod принимает один параметр типа string и возвращает одно значение того же типа.

Далее в классе DelegateTest объявляются три статических метода с одним параметром типа string и возвращаемым значением того же типа. Следовательно, они соответствуют делегату StrMod. Эти методы видоизменяют строку в той или иной форме. Обратите внимание на то, что в методе ReplасеSpaces () для замены пробелов дефисами используется один из методов типа string - Replace (). В методе Main ()

создается переменная экземпляра strOp ссылочного типа StrMod и затем ей присваивается ссылка на метод ReplaceSpaces (). Обратите особое внимание на следующую строку кода.

StrMod strOp = new StrMod(ReplaceSpaces);

В этой строке метод ReplaceSpaces () передается в качестве параметра. При этом указывается только его имя, но не параметры. Данный пример можно обобщить: при получении экземпляра делегата достаточно указать только имя метода, на который должен ссылаться делегат. Ясно, что сигнатура метода должна совпадать с той, что указана в объявлении делегата. В противном случае во время компиляции возникнет ошибка. Далее метод ReplaceSpaces () вызывается с помощью экземпляра делегата strOp, как показано ниже.

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

Экземпляр делегата strOp ссылается на метод ReplaceSpaces (), и поэтому вызывается именно этот метод. Затем экземпляру делегата strOp присваивается ссылка на