Обработка событий в net с помощью c#


Содержание

Обработка событий в WinForm C#

Есть следующая проблема, на форме расположен компонент, форма создана без элементов управления и обработка перемещения формы по рабочему столу производится «своим» кодом. В частности обратываются события клика по форме (нажал\отпустил левую кнопку мыши), перемещения указателя мыши. Компонент, который расположен на форме занимает все пространтсво формы, соответсвенно, события мыши обрабатываются для него, а не для формы. Можно ли как нибудь игнорировать эти события для компонента, но обрабатывать их для формы?

Записная книжка программиста-новичка, C#, SQL, PHP и все-все-все

Я ведь это уже делал, но хрен теперь найдешь тот кусок кода, гуглим снова… Где бы найти простое и понятное руководство для начинающего, а не тонкости для мега-гуру?

Рубрики

Свежие записи

Свежие комментарии

  • Calator prin Romania к записи Как переименовать проект/каталог проекта в Visual Studio?
  • iukovl к записи Как изменить максимальный размер загружаемого файла в php
  • sdfdsgeg к записи Работа с файлом конфигурации (configuration) в C# — читаем и сохраняем
  • iukovl к записи Как изменить максимальный размер загружаемого файла в php
  • Надежда к записи Ошибка в коде привела к убыткам в 476 миллионов долларов и банкротству компании

Архивы

Создаем собственное событие в C#

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

Для решения задачи надо внутри класса описать само событие с использованием ключевого слова event, задать делегат (проще говоря шаблон) функции-обработчика, по которому такие функции будут создаваться во внешнем коде. При желании можно еще создать собственный класс аргументов события, тех данных, которые будут передаваться к подписанной на событие функции. Если желания нет, можно использовать стандартный класс EventArgs, описывающий по сути дела пустое событие без аргументов. Если желание есть, придется наследоваться от этого класса.

Итак, мы пишем компьютерную игру. В игре есть персонаж-герой, у него есть жизнь, измеряемая числом от 1 до 100. В результате сражений жизнь может изменяться. При изменении жизни надо обновлять число на экране, если жизнь становится меньше 20, выводить предупреждение.

предположим, что игра происходит в окне Windows Forms (в жизни бывает еще и не такое) c текстовой меткой _heroHealthLabel и кнопкой _damageHeroButton

Чтобы передавать обработчику события параметры нам надо создать свой класс параметров, наследующий от EventArgs, в вышеприведенный код добавится следующее (в учебных целях сохраним старое событие и исправление добавим новым, улучшенным событием:

// в свойство Health

// в конструктор формы

// в форму обработчик, использующий добавленный в дизайнере текстбокс

// последний штрих, если мы делаем управлятор (Control), то новое свойство неплохо бы видеть в дизайнере, для Visual Studio это делается следующим образом

Если вы все еще не уверены в своих знаниях, можете скачать тестовый WinForms проект, созданный в VisualStudio 2010

.NetBlog — блог о программировании на C# .Net, и других, не мене интересных вещах.

Заметки о разработке ПО, советы по .net, SharePoint, SQL, архитектура приложений, литература по программированию. А также просто просто мысли о жизни и рассказы о том, что интересно автору, например путешествиях и вкусной еде ;)

понедельник, 17 марта 2014 г.

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

Иногда бывает необходимо отлавливать события внутри приложения вне зависимости от того, какой контрол активен. Например, вы хотите реализовать аналог «IDDQD» в своем приложении ;))) Или просто хотите обрабатывать события по каким-то своим законам. Сегодня я расскажу вас как это сделать.

Для начала создадим класс, который наследует от IMessageFilter, и создать в нем события которые будут вызываться в процессе обработке. В моем случае — это для события: для нажатия клавиши на клавиатуре и прокрутки колесика мыши. Интерфейс IMessageFilter представляет только один член — метод PreFilterMessage в котором и происходит обработка очереди событий.
Да, и для каждого события нам нужен делегат описывающий сигнатуру вызова.

следующий этап — реализовать метод PreFilterMessage:

Внутри этого метода вы можете отлавливать какие угодно события. Вот тут их относительно полный список ( http://msdn.microsoft.com/en-us/library/ms644927%28v=VS.85%29.aspx#system_defined )
Ну и последний этап — нам надо написать методы обработчиков событий. Обратите внимание на строчку получения смещения из параметра delta. В сообщении WM_MOUSEWHEEL смещение прокрутки колеса находится в старшем слове параметра. Так, что, нам нужно сдвинуть его значение вправо и обрезать до short. Это я к тому, что внимательнее читайте описание сообщений

И, добавить ваш фильтр в очередь обработки сообщений приложения. Делать это нужно в Form_Load

ГЛАВА 14. Делегаты и обработчики событий

Одно из полезных нововведений в С# — делегаты (delegates). Их назначение по сути совпадает с указателями функций в C++, но делегаты являются управляемыми объектами и привязаны к типам. Это значит, что исполняющая среда (runtime) гарантирует, что делегат указывает на допустимый объект, а это в свою очередь означает получение всех достоинств указателей функций без связанных с этим опасностей, таких как применение недопустимых адресов или разрушение памяти других объектов. В этой главе мы рассмотрим делегаты в сравнении с интерфейсами, их синтаксис и проблемы их применения. Мы также увидим несколько примеров использования делегатов с функциями обратного вызова и асинхронными обработчиками событий.

Из главы 9 мы узнали как определяются и реализуются интерфейсы. Как вы знаете, с концептуальной точки зрения интерфейсы — это связки между двумя различными частями кода. Но при этом интерфейсы во многом напоминают классы, так как объявляются в период компиляции и могут включать методы, свойства, индексаторы и события. Что касается делегата, то он ссылается на единственный метод и определяется в период выполнения. В С# две основных области применения делегатов: методы обратного вызова и обработчики событий.

Использование делегатов в качестве методов обратного вызова

Методы обратного вызова повсеместно используются в Windows для передачи указателя функции другой функции, чтобы последняя могла вызвать первую (через переданный ей указатель). Так, функция Win32 API

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

  • Асинхронная обработка Методы обратного вызова используют при асинхронной обработке, когда вызванному коду требуется существенное время для обработки запроса. Обычно сценарий таков. Клиентский код вызывает метод, передавая ему метод обратного вызова. Вызванный метод начинает работу в своем потоке и сразу возвращает управление. Запущенный поток затем выполняет основную работу, при необходимости обращаясь к функции обратного вызова. Очевидное дбстоинство такого подхода в том, что клиент продолжает работу, не блокируясь на потенциально длительное время, которое требуется для синхронного вызова.
  • Введение дополнительного кода в код класса Другой распространенный способ применения методов обратного вызова имеет место, когда класс позволяет клиенту указать метод для дополнительной нестандартной обработки. Например, в классе Windows Listbox можно указать нисходящий или восходящий порядок сортировки элементов. Кроме еще некоторых базовых возможностей для сортировки, этот класс на самом деле не дает полной свободы действий и остается общим классом. Но при этом Listbox позволяет указывать для сортировки функцию обратного вызова. Таким образом, Listbox для сортировки вызывает функцию обратного вызова, и ваш код может выполнять нужные нестандартные действия.

Рассмотрим пример определения и применения делегата. В этом примере у нас есть класс менеджера базы данных, который отслеживает все активные соединения с БД и предоставляет метод перечисления этих соединений. Если допустить, что менеджер БД находится на удаленной машине, правильным решением будет сделать метод асинхронным, позволив клиенту предоставлять метод обратного вызова. Заметьте: в реальном приложении вам следовало бы задействовать многопоточность, чтобы добиться подлинной асинхронности. Но для упрощения примера и поскольку мы не рассматривали многопоточность, пока не будем ее применять.

Для начала определим два главных класса: DBManager n DBConnection.

public delegate void EnumConnectionsCallback(DBConnection

connection); public static void EnumConnections(EnumConnectionsCallback

foreach (DBConnection connection in activeConnections)

Метод EnumConnectionsCallback является делегатом, что определяется ключевым словом delegate в начале сигнатуры метода. Как видите, этот делегат возвращает void и принимает единственный аргумент — объект DBConnection. Метод EnumConnections в соответствии с его определением принимает единственный аргумент — метод EnumConnectionsCallback. Чтобы вызвать метод DBManager.EnumConnections нам нужно лишь передать ему экземпляр делегата DBManager.EnumConnectionCallback.

Для создания экземпляра делегата нужно применить new, передав ему имя метода, имеющего ту же сигнатуру, что и у делегата. Вот пример:

Заметьте, что это можно скомбинировать в единый вызов:

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

public DBConnection(string name) <

protected string Name; public string name <

static DBConnection[] activeConnections;

public void AddConnectionsO

activeConnections = new DBConnection[5];

activeConnections[i] = new DBConnection(«DBConnection » + (i + 1)); > >

public delegate void EnumConnectionsCallback(DBConnection

connection); public static void EnumConnections(EnumConnectionsCallback

foreach (DBConnection connection in activeConnections)

public static void ActiveConnectionsCallback(DBConnection

Console.WriteLine(«Callback method called for »

public static void Main() <

DBManager dbMgr = new DBManagerO; dbMg r.AddConnections();

DBManager.EnumConnectionsCallback myCallback = new DBManager.EnumConnectionsCallback (ActiveConnectionsCallback);

После компиляции и запуска этого приложения мы получим такие результаты:

Callback method called for DBConnection 1 Callback method called for DBConnection 2 Callback method called for DBConnection 3 Callback method called for DBConnection 4 Callback method called for DBConnection 5

Определение делегатов в качестве статических членов

Довольно неуклюжее решение создавать экземпляр делегата при каждом его применении, но в С# можно определять метод, который используется при создании делегата как статический член класса. Ниже приведен предыдущий пример с применением такого подхода. Заметьте, что теперь делегат определен как статический член класса myCallback и этот член может использоваться в методе Main, так что клиенту нет нужды создавать экземпляр делегата.

public DBConnection(string name)

protected string Name; public string name <

static DBConnection[] activeConnections;

public void AddConnectionsO

activeConnections = new DBConnection[5];

activeConnections[i] = new DBConnection(«DBConnection » + (i + 1))-> >

public delegate void EnumConnectionsCallback(DBConnection connection);

public static void EnumConnections(EnumConnectionsCallback

foreach (DBConnection connection in activeConnections)

public static DBManager.EnumConnectionsCallback myCallback = new DBManager.EnumConnectionsCallback (ActiveConnectionsCallback);

public static void ActiveConnectionsCallback(DBConnection

Console.WriteLine («Callback method called for » +

public static void MainQ <

DBManager dbMgr = new DBManagerQ;

ПРИМЕЧАНИЕ Поскольку общим правилом именования делегатов является добавление слова Callback к имени метода, принимающего делегат в качестве аргумента, можно по ошибке использовать имя этого метода вместо имени делегата. При этом компилятор уведомит, что вы указали метод там, где ожидается класс. Получив такую ошибку, помните: проблема в том, что вы указали метод вместо делегата.

Создавайте делегаты только при необходимости

В двух примерах, которые мы рассмотрели, делегаты создаются независимо от того, будут ли они когда-либо использоваться. В рассмотренных примерах ничего плохого в этом нет, поскольку известно, что они будут вызываться. Но в целом при определении делегатов важно решить, когда их создавать. Скажем, может случиться, что создание делегатов потребует немалого времени и затрат. В таких случаях, когда вы знаете, что клиент обычно не обращается к данному методу обратного вызова, можно отложить создание делегата до момента, когда он действительно потребуется, заключив создание его экземпляра в оболочку свойства. Это иллюстрирует следующий измененный класс DBManager, в котором для создания экземпляра делегата используется неизменяемое свойство (поскольку для него представлен только метод-получатель), using System;

public DBConnection(string name)

protected string Name; public string name <

static DBConnection[] activeConnections;

public void AddConnections()

activeConnections = new DBConnection[5];

DBConnection(«DBConnection » + (i + 1)); >

public delegate void EnumConnectionsCallback(DBConnection

connection); public static void EnumConnections(EnumConnectionsCallback

foreach (DBConnection connection in activeConnections)

public DBManager.EnumConnectionsCallback myCallback

return new DBManager.EnumConnectionsCallback (ActiveConnectionsCallback); > >

public static void ActiveConnectionsCallback(DBConnection

(«Callback method called for » + connection.name); >

Bublic static void MainQ <

DelegateSApp app = new Delegate3App();

DBManager dbMgr = new DBManagerO; dbMgr.AddConnections();

Объединение делегатов — создание одного делегата из нескольких — одна из тех возможностей, которая поначалу не кажется такой уж полезной, но если вы столкнетесь с такой потребностью, то будете признательны команде разработчиков С# за то, что они это предусмотрели. Разберем некоторые примеры, когда может быть полезно объединение делегатов. В первом примере мы имеем дистрибьюторскую систему и класс, просматривающий все наименования товара на данном складе, вызывая метод обратного вызова для каждого наименования, запасов которого менее 50 единиц. В реальном дистрибьюторском приложении формула должна учитывать не только наличные запасы, но также заказанные и находящиеся «в пути». Но возьмем простой пример: если наличие на складе менее 50 единиц, возникает исключение.

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

public Part(string sku)

Random r = new Random(DateTime.Now.Millisecond); double d = r.NextDoubleO * 100;


protected string Sku; public string sku <

BestProg

События и делегаты. Понятие события. Взаимодействие между событиями

Содержание

1. Что такое событие в C#? Какая целесообразность использования событий в языке C#?

Событие – это автоматическое сообщение о том, что в программе состоялось некоторое действие.

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

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

На рисунке 1 схематично отображена работа события MyEvent .

Рис. 1. Вызов цепи методов для события MyEvent

2. Какая общая форма объявления события? Ключевое слово event . Пример

Объявление события осуществляется на основе ранее объявленного типа делегата. Общая форма объявления события:

  • делегат_события – имя типа делегата, который используется для объявления события;
  • имя_события – конкретное имя объекта (переменной) типа «событие».

Пример. Объявление события с именем MyEvent на основе типа делегата MyDelType .

Более подробно об объявлении типа делегата и объявлении переменной делегата описывается в теме:

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

Для того, чтобы можно было обрабатывать (запускать) списки обработчиков события, делегат не должен возвращать значение. То есть, делегат может возвращать тип void .

Если делегат возвращает значение, то вызывается последний метод (обработчик) из сформированного списка методов.

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

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

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

5. Как осуществляется регистрация метода в событии? Пример

Чтобы зарегистрировать метод для обработки данного события нужно использовать операторы ‘=’ или ‘+=’ .

Пример.

Пусть в теле некоторого класса объявляется:

  • событие с именем MyEvent ;
  • метод обработки события (обработчик события) MyMethod1() ;
  • обработчик события MyMethod2() ;
  • обработчик события MyMethod3() .

Методы имеют точно такую же сигнатуру, как и тип делегата, на основе которого объявлено событие MyEvent . Чтобы зарегистрировать методы MyMethod1() , MyMethod2() , MyMethod3() для события MyEvent нужно написать следующий программный код:

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

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

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

Чтобы организовать список обработчиков события (методов) нужно выполнить такую последовательность шагов:

  1. Объявить тип делегата в классе.
  2. Объявить событие в данном классе или создать другой класс, который содержит объявления события.
  3. В некотором методе (программном коде) создать список обработчиков (методов), которые будут вызываться при вызове данного события. Это осуществляется с помощью операторов ‘=’ и ‘+=’ . Создание списка означает регистрацию обработчиков для данного события.
  4. Вызвать событие (запустить на выполнение) из этого метода.

7. Пример использования события для случая если методы и события объявлены в одном классе

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

Пусть в модуле CalcFigures.cs объявлен класс, содержащий следующее описание

В другом модуле Form1.cs объявлен класс, который демонстрирует использование методов из класса CalcFigures . Листинг модуля Form1.cs следующий:

Основы, лучшие методы и соглашения реализации событий в C#

Written on 18 Июня 2013 . Posted in C#.NET

ОГЛАВЛЕНИЕ

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

1. Введение

Все, что вам нужно, чтобы определить, реализовать и понять пользовательские события с помощью C#, изложено в данной статье. Представлены важнейшие компоновочные модули, которые должны использоваться для достижения данных целей, наряду с лучшими методами и соглашениями реализации событий. Данная статья описывает варианты .NET 1.x и 2.0+ для публикации и подписки на события.

Хотя поддержка реализации пользовательских событий была доступна с версии 1.0 среды разработки .NET, с тех пор были добавлены дополнительная связанная с событиями поддержка и возможности. Некоторые из новых возможностей (например, обобщенный System.EventHandler, безымянные методы, вывод делегатов и т.д.) образуют сокращения, предназначенные для упрощения реализации событий. Хотя такие методы ускоряют реализации событий, предъявление их до или вместо важнейших компоновочных модулей дало бы менее точное представление. Поэтому данная статья избегает таких сокращений до тех пор, пока не будут введены все важнейшие компоновочные модули.

Предположения об аудитории

Данная статья предполагает знания программирования .NET с C#, наряду с пониманием общих средств, внедренных в версию 2.0 среды разработки .NET. Если вы не понимаете общие средства, эта статья все же может быть полезной, так как в ней изложены способы реализации событий, не основанные на общих средствах. В данной статье представлены общие и не общие методы реализации событий.

2. Терминология и определения

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

событие, предсобытие, постсобытие, и состояние, изменение состояния и ожидаемое изменение состояния

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

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

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

публикатор события, источник события, предмет

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

подписчик события, получатель, слушатель, наблюдатель

Это классы или объекты, интересующиеся изменениями в состоянии (или ожидаемыми изменениями в состоянии) публикаторов событий. Эти термины ссылаются на классы или объекты, обычно выполняющие некоторое действие в ответ на возникновение события.

возбудить, запустить, или вызвать событие; уведомление, или уведомление о событии

Уведомления о событиях (часто выражаемые в виде, «запустить событие» или «возбудить событие» или «вызвать событие») обычно имеют форму публикатора события, вызывающего метод в одном или более подписчиков. Следовательно, возбуждение события в итоге означает, что код в публикаторе события вызывает выполнение кода в одном или более подписчиков.

В случаях, когда никакие подписчики [на событие] не были зарегистрированы в публикаторе, событие не возбуждается.
Заметьте, что в данной статье события описаны как «возбужденные» (не «запущенные» или «вызванные»). Это соглашение исходит от группы разработчиков, создавших большую часть среды разработки .NET (Квалина и Абрамс, 2006). Они предпочитают термин, «возбудить», так как он не имеет дополнительных негативных значений выражений «запустить» или «вызвать».

данные о событии, связанные с событием данные, и аргументы события («event args»)

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

Например, событие может быть возбуждено, когда файл переименовывается. Данные, относящиеся к данному конкретному событию «файл переименован», могут включать (1) имя файла до изменения имени, и (2) имя файла после изменения имени. Эти имена файлов могут образовать данные о событии, отправляемые подписчикам во время возбуждения события «файл переименован».

Тип делегата, делегаты

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

Два значения «обработчика события»

Литература за пределами данной статьи часто использует термин, «обработчик события», в отношении (1) делегата, на основе которого определено событие (в публикаторе), или (2) любого метода, зарегистрированного в событии (в подписчике). Более того, Intellisense(«интеллектуальное восприятие») в Visual Studio ссылается на метод обработки события (в подписчике) просто как на «обработчик». Для ясности данная статья использует выражение «обработчик события» в отношении делегата, тогда как выражение «метод обработки события» используется в отношении любого метода, зарегистрированного в событии.

Для обобщения: «обработчик события» – делегат, на котором событие основано, тогда как «метод обработки события» – метод, вызываемый в подписчике, когда событие возбуждается.

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

Илон Маск рекомендует:  RandSeed - Переменная Delphi

События .NET и шаблон наблюдателя GoF

События, реализованные в среде разработки .NET и описанные в данной статье, образуют оптимизированную реализацию .NET шаблона наблюдателя, документированного «бандой четырёх» или «GoF» (Гамма и другие, 1995). Механизмы .NET, используемые для реализации событий (особенно делегатов), существенно сокращают объем работ, требуемый для реализации шаблона наблюдателя в приложениях .NET.

3. Делегаты

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

3.1 Определение и использование делегатов

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

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

Заметьте, что объявление делегата выглядит как объявление метода, но без тела метода.
Сигнатура делегата определяет сигнатуру методов, на которые может ссылаться делегат. Пример делегата выше (MyDelegate) может хранить ссылки только на методы, возвращающие void, при этом принимая единственный строковый аргумент. Следовательно, следующий метод может быть зарегистрирован в экземпляре MyDelegate:

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

После создания экземпляра делегата в нем могут быть зарегистрированы дополнительные методы, таким образом:

Теперь делегат можно вызвать так:

Так как MyMethod и MyOtherMethod зарегистрированы в экземпляре MyDelegate (названным del), этот экземпляр вызовет MyMethod и MyOtherMethod, когда выполнится строка кода выше, передав каждому из методов строковое значение «мое строковое значение».

Делегаты и Перегруженные методы

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

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

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

. компилятор C# зарегистрирует только конкретную перегрузку с совпадающей сигнатурой. Из следующих двух перегрузок только первая была бы зарегистрирована в экземпляре anotherDel типа MyOtherDelegate:

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

3.2 В чем необходимость делегатов?

Если это ваше первое знакомство с делегатами, вы можете задаться вопросом: «Зачем утруждаться, не проще ли вызвать метод напрямую? Какая польза от применения делегата?»

Необходимая косвенность

Краткий ответ (на вопрос выше «зачем утруждаться?») заключается в том, что написанный нами код или используемые нами компоненты не могут всегда знать, какой конкретный метод вызывать в конкретный момент времени. Одна важная перспектива делегатов в том, что они предоставляют компонентам .NET способ вызова вашего кода без необходимости для компонента .NET знать что-либо о вашем коде, кроме сигнатуры метода (требуемой типом делегата). Например, компоненты среды разработки .NET, такие как компонент таймера, часто должны выполнять написанный вами код. Так как компонент таймера не может знать, какой конкретный метод вызывать, он задает тип делегата (и, стало быть, сигнатуру метода), который будет вызываться. Затем вы подключаете ваш метод – с требуемой сигнатурой – к компоненту таймера, регистрируя ваш метод в экземпляре делегата типа делегата, ожидаемого компонентом таймера. Компонент таймера затем может запустить ваш код, вызвав делегат, который, в свою очередь, вызовет ваш метод.

Заметьте, что компонент таймера по-прежнему ничего не знает о вашем конкретном методе. Компонент таймера знает только о делегате. Делегат, в свою очередь, знает о вашем методе, так как вы зарегистрировали ваш метод в этом делегате. Итог в том, что компонент таймера запускает ваш метод, ничего не зная о вашем конкретном методе.
Точно так же, как и пример компонента таймера выше, можно использовать делегаты таким образом, который позволяет вам писать ваш собственный код без необходимости для нашего кода знать конкретный метод, который в конечном счете будет вызван в конкретной точке. Вместо того чтобы вызывать метод в той точке, код может вызвать экземпляр делегата — который, в свою очередь, вызывает все методы, зарегистрированные в экземпляре делегата. Итог в том, что совместимый метод вызывается, хотя конкретный метод, который будет вызываться, не был непосредственно записан в нашем коде.

Синхронный и асинхронный вызов метода

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

Основа события

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

Другие применения

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

3.3 Внутреннее устройство делегатов


Объявление делегата приводит к созданию нового класса

Объявление делегата, которое вы напишете, достаточно для определения полного нового класса делегата. Компилятор C# берет ваше объявление делегата и вставляет новый класс делегата в выходную сборку. Имя этого нового класса – имя типа делегата, заданное вами в объявлении делегата. Сигнатура, заданная в вашем объявлении делегата, становится сигнатурой методов в новом классе, используемом для вызова любого/всех из методов, на которые ссылается делегат (особенно методы Invoke и BeginInvoke). Этот новый класс расширяет (наследует) System.MulticastDelegate. Поэтому большинство из методов и свойств, доступных в вашем новом классе делегата, взяты из System.MulticastDelegate. Методы Invoke, BeginInvoke и EndInvoke вставляются компилятором C#, когда он создает новый класс в выходной сборке (это методы, которые можно вызвать, чтобы заставить делегат вызвать любой/все методы, на которые он ссылается, — Invoke для синхронного вызова, и BeginInvoke и EndInvoke, используемые в асинхронных вызовах).

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

Например, когда компилятор C# встречает следующее объявление делегата.

. компилятор вставляет новый класс по имени MyFabulousDelegate в выходную сборку. Методы Invoke, BeginInvoke и EndInvoke класса MyFabulousDelegate содержат параметр int и возвращаемое значение string в своих соответствующих сигнатурах методов.

Нужно отметить, что MulticastDelegate – особый класс в том смысле, что компиляторы могут наследовать от него, но вы не можете явно наследовать от него. Ваше использование ключевого слова C# delegate и связанный с ним синтаксис являются методом приказания компилятору C# расширить MulticastDelegate для ваших целей.

Смысл многоадресности

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

Делегаты неизменяемые

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

Делегаты не являются указателями функций

Наконец, программисты C и C++ признают, что делегаты похожи на указатели функций в стиле C. Но важное отличие в том, что делегат – не просто указатель на непосредственный адрес памяти. Наоборот, экземпляры делегатов являются типизированными объектами, управляемыми .NET CLR(общеязыковая среда исполнения) и ссылаются на один или более методов (в отличие от адресов памяти).

3.4 Все делегаты одинаковые (отсутствуют радикально различающиеся типы делегатов)

Все эти утверждения верны:

«Если вы видели один делегат, вы видели их все».
или
«Все делегаты созданы равными»
или
«Делегат является делегатом является делегатом»

Когда вы читаете о разных типах делегатов, вы должны понимать, что внутри все делегаты одинаковы. Это верно для делегатов, предоставленных средой разработки .NET, и для делегатов, создаваемых вами для ваших собственных целей. Утверждение «все они одинаковые», в частности, означает, что все делегаты (1) наследуются от System.MulticastDelegate, который, в свою очередь, наследуется от System.Delegate; и (2) предоставляют одинаковый набор членов, включая методы Invoke,BeginInvoke, и EndInvoke() и т.д.

Типы делегатов различаются только по таким параметрам,как:
1. Имя типа делегата.
2. Сигнатура делегата — включая тип возвращаемой переменной и количество и типы параметров.
3. Предусмотренное применение или роль делегата.
Возьмем, например, обобщенный делегат предикат (System.Predicate ). Ниже перечислено то, что делает его «делегатом предикат»:
1. Имя типа: Predicate
2. Сигнатура: возвращает bool, принимает единственный параметр типа object, для которого тип, будучи обобщенным, может быть установлен на этапе проектирования.
3. Предусмотренное применение или роль: этот делегат будет ссылаться на метод, определяющий набор критериев и устанавливающий, соответствует ли заданный объект данным критериям.

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

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

Касательно перспективы предусмотренного применения: вы имеете право использовать любой делегат для целей, не предусмотренных создателями делегата – так как делегаты не связаны с каким-то конкретным применением. Вы можете, например, использовать делегат Predicate для вызова любого метода, возвращающего bool и принимающего единственный параметр типа object – даже если эти методы не определяют, соответствует ли заданный объект какому-либо критерию (что является предусмотренным применением делегата Predicate ). Вы не должны использовать делегаты для целей, отличных от тех, которым они предназначены служить, так как ценность среды разработки .NET, предоставляющей предварительно подготовленные делегаты (такие как Predicate ) в том, что можно понять выполняемую ими роль без необходимости перерывать кучу кода, чтобы выяснить, что они на самом деле делают.

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

4. Связь между делегатами и событиями

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

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

4.1 Обработчики событий (в целом)

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

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

Пользовательские обработчики событий

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

Строка 1 объявляет тип делегата, для которого может быть назначен любой метод — при условии, что метод возвращает void и принимает единственный аргумент string:
• public – область видимости, указывающая, что объекты вне нашего класса могут ссылаться на делегат. Если тип делегата объявлен внутри класса публикации события, то он должен иметь общедоступную область видимости, чтобы подписчики события могли видеть его и объявлять его экземпляры, в которых они должны регистрировать свои методы обработки события (подробнее об этом позже).
• delegate – ключевое слово, применяемое для объявления пользовательских делегатов в среде разработки .NET.
• void — тип возвращаемой переменной. Это часть сигнатуры делегата, и соответственно тип возвращаемой переменной, который регистрирующие методы должны задавать.
• MyDelegate – имя типа делегата.
• (string whatHappened) – остальная часть сигнатуры. Любой метод, регистрирующийся в событии, должен принимать единственный аргумент string (наряду с возвращением void).

Строка 2 объявляет событие в виде типа делегата. Заметьте, что событие (названное MyEvent) объявлено очень похоже на объявление метода – но его тип данных задан как тип делегата:
• public — область видимости, указывающая, что объекты вне нашего класса могут подписываться на событие.
• event – ключевое слово, применяемое для определения события.
• MyDelegate – тип данных события (это пользовательский тип делегата, определенный в строке 1.)
• MyEvent – имя события.

Делегат, объявленный в строке 1, — всего лишь обычный делегат (как и все делегаты), и может использоваться для любой цели, которую делегаты могут выполнить. Строка 2 (т.е., использование типа делегата) превращает делегат в обработчик события. Чтобы сообщить, что конкретный тип делегата применяется в качестве обработчика события, появился способ именования, при котором имя типа делегата заканчивается на «обработчик» (подробнее об этом позднее).

Стандартизированные обработчики событий

Хотя вы можете создавать свои собственные обработчики событий (и иногда вам может это понадобиться), вы должны использовать один из делегатов EventHandler, предоставляемых средой разработки .NET, в случаях, когда один из обработчиков событий среды разработки может работать с вашей конкретной реализацией события. Многие события используют обработчики событий, имеющие общие или одинаковые сигнатуры. Поэтому не загромождайте ваш исходный код кучей делегатов, различающихся лишь по имени типа, а используйте встроенные обработчики событий, так как такой подход сокращает объем кода, который вам понадобится написать и обслуживать, и делает ваш код более понятным. Если некто, читающей ваш код, видит, что вы основываете событие на делегате System.EventHandler , то он автоматически много узнает о вашей реализации события без необходимости смотреть дальше.

4.2 Необобщенный делегат System.EventHandler

Доступный в версии 1.x среды разработки .NET, необобщенный делегат System.EventHandler навязывает условное обозначение (подробнее описано ниже) обработчиков событий, не возвращающих никакого значения, в то же время принимающих два параметра: первый является параметром типа object (чтобы хранить ссылку на класс, возбуждающий событие), второй параметр типа System.EventArgs или его подкласс (чтобы хранить любые данные о событии). System.EventArgs представлен далее.

Так среда разработки .NET объявляет делегат System.EventHandler.

4.3 Обобщенный делегат System.EventHandler

Доступный начиная с версии 2.0 среды разработки .NET, обобщенный делегат System.EventHandler навязывает такое же соглашение сигнатуры, какое навязывает необобщенная версия, но принимает параметр обобщенного типа в качестве второго параметра System.EventArgs.

Объявление этого встроенного делегата навязывает ограничение, чтобы тип TEventArgs принадлежал к типу System.EventArgs (включая его подклассы):

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

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

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

5. Аргументы события (EventArgs)

Аргументы события – иногда именуемые «event args» – образуют данные, отправляемые публикатором события подписчикам в ходе возбуждения события. Видимо, эти данные относятся к возникновению события. Например, когда возбуждается событие «файл был только что удален», аргументы события, скорее всего, будут состоять из имени файла до изменения имени, а также имени файла после изменения его имени. Методы обработки события могут считывать аргументы события (называемые «данные события»), чтобы узнать больше о возникновении события.

5.1 Роль System.EventArgs

Есть два основных варианта включения аргументов события в ваши события.
1. Вы можете инкапсулировать все аргументы события в виде свойств класса, полученного от System.EventArgs. Во время выполнения экземпляр этого класса отправляется подписчикам события, когда событие возбуждено. Подписчики события считывают аргументы события в виде свойств этого класса.
2. Вы можете избежать использования System.EventArgs и взамен объявить отдельные аргументы события — подобно тому, как вы включили бы аргументы в объявление метода. Причины невыгодности такого подхода описаны в разделе 5.2.

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

Некоторые события не содержат никаких данных. В этих случаях System.EventArgs используется как метка-заполнитель, в основном для сохранения одной единообразной сигнатуры обработчика события во всех событиях, независимо от того, содержат события данные или нет. В случае событий без данных публикатор события отправляет значение System.EventArgs.Empty во время возбуждения события.

5.2 Расширение System.EventArgs

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

Чтобы показать компромиссы, связанные с отправкой экземпляра подкласса System.EventArgs по сравнению с отправкой отдельных аргументов события, рассмотрим сценарий, в котором вам нужно добавить единственное значение типа string в данные события. Если бы вы задали данные события в виде отдельных параметров в сигнатуре делегата (а не путем создания производного класса от System.EventArgs), то все подписчики вашего события пришлось бы изменять, чтобы они принимали один дополнительный параметр string. Даже подписчики, не интересующиеся этим дополнительным значением string, пришлось бы изменить, чтобы они принимали это значение, так как сигнатура пользовательского обработчика событий изменилась бы. Если бы вместо этого вы создали производный класс от System.EventArgs, то вам пришлось бы лишь добавить новое свойство string в ваш класс. Сигнатура события не изменилась бы, и, следовательно, не изменилась бы сигнатура обработчика события, используемая в любом из существующих подписчиков. Подписчики, не интересующиеся новым свойством string, не пришлось бы изменять, так как сигнатура обработчика события не изменилась бы – и они могли бы игнорировать дополнительное свойство string. Подписчики, интересующиеся новым значением string, смогли бы считать его в виде свойства подкласса EventArgs.

Здесь приведен пример подкласса EventArgs, инкапсулирующего единственное значение string:

5.3 Роль System.ComponentModel.CancelEventArgs

System.ComponentModel.CancelEventArgs получен от System.EventArgs, и существует для поддержки отменяемых событий. Кроме членов, предоставляемых EventArgs, CancelEventArgs предоставляет булево свойство Cancel, которое при установке в true подписчиком события, используется публикатором события для отмены события.

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

6. Синтаксис объявления события

6.1 Варианты синтаксиса объявления события

Ключевое слово event используется для формального объявления события. Есть два правильных варианта синтаксиса объявления события. Независимо от того, какой синтаксис вы примените, компилятор C# преобразует оба объявления свойства в следующие три компонента в выходной сборке.

1. Обработчик события с закрытой областью действия (или функционально эквивалентная структура данных). Делегат имеет закрытую область действия, чтобы запретить вызов события внешним кодом, тем самым сохраняя инкапсуляцию.
2. Метод Add с открытой областью действия; используется для добавления подписчиков в закрытый обработчик события.
3. Метод Remove с открытой областью действия, применяемый для удаления подписчиков из закрытого обработчика события.

Полеподобный синтаксис объявляет событие в одной или двух строках кода (одна строка- для события, другая- для связанного с ним обработчика события, если/когда не используется встроенный делегат EventHandler).

2. Свойствоподобный синтаксис

Свойствоподобный синтаксис выглядит очень похожим на типичное объявление свойства, но с явно заданными блоками add и remove вместо блоков «получатель» и «установщик». Вместо того, чтобы возвращать или устанавливать значение закрытой переменной члена, они добавляют и удаляют входные экземпляры делегата в/из нижележащего обработчика события или другой структуры данных, играющей аналогичную роль.

Рекомендации по организации поточной обработки

Полеподобный синтаксис автоматически является поточно-ориентированным:

Свойствоподобный синтаксис придется специально делать поточно-ориентированным. Ниже приведен поточно-ориентированный вариант:

Вы можете опустить блоки lock<> и объявление переменной padlock, если потоковая безопасность не требуется.

6. 2 Рекомендации по выбору между полеподобным синтаксисом и свойствоподобным синтаксисом

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

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

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

6.3 Механизм публикации/подписки при помощи делегатов без событий (никогда не делайте так)

Должно быть ясно, что события не являются делегатами – хотя события сильно зависят от делегатов и в некоторых случаях могут рассматриваться как форма реализации делегата. События также не являются экземплярами делегатов, хотя события могут использоваться очень похожим образом.

Хотя вы можете пропустить ключевое слово «событие» (и, соответственно, формальное объявление события) и использовать открытый делегат для предоставления механизма уведомлений о публикации-и-подписке, вы никогда не должны так делать. Недостаток открытых делегатов (по сравнению с объявлением события) в том, что методы вне публикующего класса могут заставлять делегаты с открытой областью действия вызывать методы, на которые ссылаются данные делегаты. Это нарушает базовые принципы инкапсуляции и может быть источником серьезных трудно устранимых проблем (ситуация гонки, и т.д.). Поэтому вы должны реализовывать события только с помощью ключевого слова event. Когда делегаты реализуются для поддержки событий, делегат – даже если он объявлен в качестве открытого члена определяющего класса – может быть вызван только изнутри класса (путем возбуждения события), а другие классы могут только подписываться на и отписываться от нижележащего делегата с помощью события.

Илон Маск рекомендует:  Тег ins

7. Код возбуждения события

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

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

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

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

Любые необработанные исключения, возбужденные в методах обработки события в подписчиках, будут переданы публикатору события. Поэтому возбуждение события должно выполняться только внутри блока try/catch:

События могут иметь несколько подписчиков, каждый из которых по очереди вызывается обработчиком события (делегатом), когда обработчик события вызывается с помощью строки [handler (this, eventArgs)]. Обработчик события, используемый в вышеприведенном блоке кода, перестает повторять свой список вызовов (подписанных на него методов обработки события), когда первое необработанное исключение возбуждается подписчиком. Например, если имеется 3 подписчика, и 2-й подписчик выбрасывает необработанное исключение при вызове его делегатом, то 3-й подписчик вообще не получит уведомления о событии. Если вы хотите, чтобы все подписчики получали уведомление о событии, даже если другие подписчики выбрасывают необработанные исключения, воспользуйтесь следующим кодом, явно проходящим в цикле по списку вызовов обработчика события:

8. Регистрация и отмена регистрации подписчика события

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

8.1 Регистрация подписчика

Чтобы подписаться на событие, подписчику нужны три вещи:
1. Ссылка на объект, публикующий нужное ему событие
2. Экземпляр делегата, на основе которого определено событие
3. Метод, который будет вызван публикатором, когда он возбудит событие
После этого подписчик регистрирует свой экземпляр обработчика события (делегата) в публикаторе таким образом:

В вышеприведенной строке:

• thePublisher – ссылка на объект, который будет возбуждать нужное событие. Обратите внимание, как событие EventName вызывается, словно оно является открытым свойством thePublisher.
• Оператор += используется для добавления экземпляра делегата в список вызовов обработчика события в публикаторе. Помните, что несколько подписчиков могут регистрироваться в событии. Используйте оператор += для добавления текущего подписчика в конец списка вызовов нижележащего делегата.
• MyEventHandlerDelegate – ссылка на конкретный делегат обработчика события, который будет использоваться (если это не один из встроенных делегатов EventHandler).
• Наконец, EventHandlingMethodName передает в класс-подписчик имя метода, который будет вызван при возбуждении события.

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

8.2 Отмена регистрации подписчика

Подписчика можно лишить регистрации в публикаторе таким образом:

Оператор -= применяется для удаления экземпляра делегата из списка вызовов в публикаторе.
Регистрация подписчиков автоматически отменяется, когда объект уничтожается – если регистрация подписчика в событии еще не была отменена явно.

9. Метод обработки события

Метод обработки события — метод в подписчике события, выполняемый публикатором события при возбуждении события. Знайте, что некоторая литература, описывающая события в .NET, называет эти методы «обработчиками события», хотя, если быть технически точным, «обработчик события» — это делегат, на котором основано событие – а не любой метод, на который ссылается такой делегат.

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

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

10. Рекомендации по .NET 1.x в сравнении с 2.0+

Концепции и возможности, представленные в данном разделе, были внедрены в версию 2.0 среды разработки .NET. Эти новые возможности означают сокращение и, возможно, упрощение вашего кода при умелом использовании.
Но есть риск, что неправильное использование некоторых из этих возможностей может сделать малопонятным ваш код реализации события. Например, если вы воспользуетесь «безымянным методом» (изложен ниже), состоящим из 30+ строк кода, ваша реализация события, скорее всего, будет намного труднее для понимания, чем эквивалентная реализация, помещающая эти 30+ строк в именованный метод.

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

10.1 Обобщенные

Наряду со специфичными для обобщенных возможностями, представленными в других местах данной статьи (например, System.EventHandler ), нужно заметить, что любые способы реализации событий, каким-либо образом основанные на обобщенных, не будут доступны в приложениях .NET 1.x, так как обобщенные были впервые введены в версии 2.0 среды разработки .NET.

10.2 Вывод делегатов

Компилятор C# 2.0 (и новее) достаточно разумен, чтобы определить тип делегата, на основе которого реализовано конкретное событие. Эта возможность «вывода делегатов» позволяет вам пропускать объявление требуемого делегата в коде, регистрирующем метод обработки события в событии.

Рассмотрим следующий код 1.x, регистрирующий метод обработки события в событии. Этот код явно создает экземпляр обработчика события (делегата), чтобы зарегистрировать связанный с ним метод в событии.

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

При назначении имени метода непосредственно событию таким образом компилятор C# следит за тем, чтобы сигнатура метода совпадала с сигнатурой обработчика события, на котором основано событие. Затем компилятор C# вставляет нужный код регистрации делегата (т.е., . += newMyEventHandlerDelegate(EventHandlingMethodName);) в выходную сборку.

Компилятор C# позволяет использовать этот упрощенный синтаксис, а не какие-либо изменения основных способов реализации событий в среде разработки .NET. События в C# 2.0 (и новее) не могут напрямую ссылаться на методы. Компилятор добавляет [все еще] требуемый синтаксис делегата в выходную сборку — словно мы явно создали экземпляр делегата.

10.3 Безымянные методы


Безымянный метод – блок кода, который вы передаете делегату (вместо передачи имени метода, на который будет ссылаться делегат). Когда компилятор C# встречает безымянный метод, он создает в выходной сборке полноценный метод, содержащий переданный вами блок кода. Компилятор задает имя для метода, затем создает ссылку на этот [новый] метод из связанного с ним экземпляра делегата (все это происходит в выходной сборке). Метод называется безымянным, потому что вы используете метод, не зная его имени (метод не имеет имени в вашем исходном коде).

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

Вышеуказанный код можно переписать с использованием безымянного метода так:

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

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

10.4 Частичные классы

Частичные классы имеют отношение к реализации событий в том, что Visual Studio поместит заглушки кода регистрации события и метода обработки события в файлы частичного класса, связанного с конкретным классом Windows Form. Нажмите здесь, чтобы перейти в раздел 15.1, подробнее описывающий реализацию событий в частичных классах.

11. Соглашения

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

11.1 Соглашения публикатора события

Имя события

• Выберите имя, четко передающее изменение состояния, обозначаемое событием.
• События могут быть разделены на следующие категории: (1) события, возбуждаемые до того, как происходит изменение состояния; и (2) события, возбуждаемые после того, как происходит изменение состояния. Соответственно, нужно выбирать имя события, говорящее, до или после изменения состояния возбуждается событие.

Примеры имен для событий, возбуждаемых до изменения состояния:
• FileDownloading
• TemperatureChanging
• MailArriving

Примеры имен для событий, возбуждаемых после изменения состояния:
• FileDownloadCompleted
• TemperatureChanged
• MailArrived

Производный класс System.EventArgs (где применимо)

• Для событий, которые должны или могут [когда-нибудь] содержать пользовательские данные о событии, вы должны создать новый класс, который (1) расширяет System.EventArgs, и (2) реализует члены (например, свойства), требуемые для того, чтобы содержать и предоставлять ваши пользовательские данные о событии
• Вы не должны создавать производный класс EventArgs, только если уверены, что ваше событие никогда не будет содержать данные о событии
• Именем вашего производного класса EventArgs должно быть имя события с добавленным в конец ‘EventArgs’

Примеры имен производных классов от EventArgs:
• DownloadCompletedEventArgs
• TemperatureChangedEventArgs
• MailArrivedEventArgs

Для событий, которые не содержат, и никогда не будут содержать данных, рекомендуется передавать System.EventArgs.Empty. Эта рекомендуемая практика служит для соблюдения соглашений реализации событий даже для событий, не имеющих данных о событии. Если существует вероятность, что ваше событие может когда-нибудь содержать данные о событии, даже если не в первоначальной его реализации, то вы должны создать производный класс от System.EventArgs и использовать его в ваших событиях. Основная польза этой рекомендации в том, что вы однажды сможете добавить данные (свойства) в ваш производный класс, не нарушая совместимость с существующими подписчиками.

Имя обработчика события (делегата)

• Если вы используете среду разработки .NET 1.x, то вы должны использовать встроенный делегат System.EventHandler
• Если вы используете среду разработки .NET 2.0 или новее (для обоих публикаторов и подписчиков), то вы можете воспользоваться обобщенным делегатом System.EventHandler
• Если вы создаете свой собственный делегат, то имя делегата должно состоять из имени события с добавленным в конец словом ‘Handler(обработчик)’

Примеры имен пользовательских обработчиков событий (делегатов):
• DownloadCompletedHandler
• TemperatureChangedHandler
• MailArrivedHandler

Сигнатура обработчика события (делегата)

Как сказано выше, в «имя делегата», вы должны использовать один из делегатов System.EventHandler, предоставленных средой разработки .NET. В этих случаях сигнатура делегата определяется для вас и автоматически соответствует рекомендуемым соглашениям.

Следующие рекомендации реализованы в делегатах System.EventHandler, предоставленных средой разработки .NET. Если вы создаете свои собственные обработчики событий, то вы должны следовать этим рекомендациям, чтобы сохранять соответствие с реализацией среды разработки .NET.

• Делегат всегда должен возвращать void.

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

• Первый параметр должен иметь тип object и должен называться sender.

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

Тип данных для sender – объект, так как практически любой класс может возбуждать события. Отсутствие строгой типизации параметра sender позволяет применять одну единственную сигнатуру обработчика события для всех событий. При необходимости метод обработки события может привести параметр отправителя к конкретному типу публикатора события.

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

• Второй параметр должен называться ‘e’ и должен иметь тип System.EventArgs или тип вашего собственного производного класса от System.EventArgs (например,MailArrivedEventArgs).

В случае отменяемых событий второй параметр имеет тип System.ComponentModel.CancelEventArgs или тип вашего собственного производного от него класса. В случае событий, не содержащих данных о событии, вы должны задать System.EventArgs в качестве типа второго параметра. В таких случаях System.EventArgs.Empty указывается в качестве значения этого параметра, когда событие возбуждается. Эта практика рекомендуется для сохранения соответствия соглашению – чтобы все сигнатуры обработчиков событий включали в себя параметр EventArgs – даже для событий, не имеющих EventArgs. Согласно соглашению, иметь одну единообразную сигнатуру более важно, чем иметь несколько сигнатур обработчиков событий – даже в случаях, когда один из параметров никогда не будет использоваться.

Примеры (без пользовательских данных, отправляемых вместе с событием):

Объявление события

• При условии, что событие подлежит предоставлению в распоряжение кода вне публикующего класса, событие должно объявляться с ключевым словом public (чтобы сделать его доступным для кода вне публикующего класса).
• Обработчик события, на котором основано событие, указывается в качестве типа события – аналогично тому, как тип данных указывается в типичном объявлении свойства или метода.

Пример (использует встроенный обобщенный делегат System.EventHandler ):

Метод, возбуждающий событие

• Вместо возбуждения события, встраиваемого в весь ваш код многократно, рекомендуется создавать отдельный метод, отвечающий за возбуждение события. Затем вы вызываете этот метод во всем вашем коде при необходимости.
• Именем этого метода должно быть слово On с добавленным в конец именем события.
• Если ваше событие использует пользовательский подкласс EventArgs, то метод, возбуждающий событие, должен принимать как минимум один параметр, принадлежащий к конкретному подклассу EventArgs, определенному для пользовательских данных события.
• Для нестатических открытых классов метод должен быть реализован в виде virtual с доступностью, установленной в protected(защищенный), чтобы производные классы легко могли оповещать клиентов, зарегистрированных в базовом классе.
• Для закрытых классов доступность метода должна быть установлена в private(закрытый), так как возбуждение событий не должно запускаться извне класса.

Примеры (каждый принимает тип пользовательского подкласса EventArgs в качестве аргумента):

11.2 Соглашения подписчика события

Имя метода обработки события

• Соглашение, реализуемое Visual Studio, когда она автоматически создает заглушку метода обработки события, заключается в том, чтобы называть метод так: (1) имя объекта, возбуждающего событие; за ним следует (2) символ подчёркивания; (3) в конец добавляется имя события.

Примеры:
• downloader_DownloadCompleted
• weatherStation_TemperatureChanged
• mailManager_OnMailArrived
• Другое соглашение по заданию имени метода обработки события совпадает с описанным выше для задания имени метода, возбуждающего событие в публикаторе. В частности, именем метода должно быть слово On с добавленным в конец именем события.

Примеры:
• OnDownloadCompleted
• OnTemperatureChanged
• OnMailArrived

Сигнатура метода обработки события

• Сигнатура метода обработки события должна точно совпадать с сигнатурой делегата. Согласно соглашениям обработки событий, а также делегатов EventHandler, предоставленных средой разработки .NET, метод обработки события должен возвращать void, при этом принимая ровно два параметра: переменная типа object, имеющая имя sender, и экземпляр EventArgs (или производного класса), имеющий имя ‘e’.

Подписка на событие (код, регистрирующий метод обработки события в событии)

• Чтобы зарегистрировать метод в событии, используйте синтаксис +=, согласно этому образцу:

Предупреждение: Не используйте оператор = при регистрации подписчика события в публикаторе. Такой способ заменит любые/все зарегистрированные в данный момент подписчики события на текущий подписчик. Вместо этого обязательно используйте оператор +=, чтобы добавить текущий подписчик в конец списка вызовов обработчика события.

Отмена подписки на событие (код, отменяющий регистрацию метода обработки события в событии)

• Чтобы отменить регистрацию метода на событие, используйте синтаксис -=, согласно этому образцу:

11.3 Соглашения об именовании

Горбатый регистр

Горбатый регистр – соглашение об именовании, при котором первая буква имеет нижний регистр, а каждая последующая «часть слова» начинается с буквы верхнего регистра. По соглашению, имена переменных пишутся в горбатом регистре.
Примеры горбатого регистра: someStringToWrite, ovenTemperature, latitude

Регистр Паскаля

Регистр Паскаля — соглашение об именовании, при котором каждая «часть слова» имени начинается с буквы верхнего регистра, с остальными буквами нижнего регистра, и без символов подчеркивания. По соглашению, имена классов, событий, делегатов, методов, и свойства пишутся в регистре Паскаля.

Примеры регистра Паскаля: MailArrivedEventHandler, AppClosing, MyClassName

12. Шаги к созданию пользовательских событий

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

12.1 Подготовка публикатора события

Шаг 1: EventArgs – Решите, как ваше событие будет вызывать EventArgs.

• Включение EventArgs в пользовательские события необходимо для соответствия стандартам публикации события. Однако EventArgs не является техническим требованием – можно создавать, возбуждать и обрабатывать пользовательские события, вообще не использующие EventArgs.
• Если ваше событие никогда не будет передавать пользовательские данные события, то вы можете воспользоваться встроенным классом System.EventArgs. Позже вам придется задать значение EventArgs.Empty при возбуждении события.
• Если ваше событие не отменяемое и содержит пользовательские данные события, то вы должны создать класс, расширяющий System.EventArgs. Ваш пользовательский подкласс EventArgs должен содержать любые дополнительные свойства, содержащие данные события.
• Если ваше событие отменяемое, то вы можете использовать System.ComponentModel.CancelEventArgs – включающий в себя булево свойство Cancel, которое клиенты могут установить в истину, чтобы отменить событие. Вы можете создать производный класс от CancelEventArgs, содержащий свойства для любых дополнительных связанных с событием данных.

Шаг 2: Обработчик события – Решите, какой обработчик события ваше событие будет использовать.

• У вас есть два основных варианта – создать свой собственный обработчик события (делегат) или использовать один из делегатов EventHandler, предоставляемых средой разработки .NET. Если вы используете один из встроенных обработчиков событий, то вам придется поддерживать меньше кода, и сигнатура вашего обработчика события будет автоматически соответствовать соглашению возвращения void, при этом принимая параметры object sender, и EventArgs e
• При использовании .NET 1.x обдумайте использование встроенного делегата System.EventHandler.
• При использовании .NET 2.0 обдумайте использование встроенного обобщенного делегата System.EventHandler .

Шаг 3: Объявление события – Решите, какой синтаксис использовать: полеподобный синтаксис или свойствоподобный синтаксис.

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

Шаг 4: Метод, возбуждающий событие – Решите, будете ли вы возбуждать событие из метода, или будете возбуждать его непосредственно в коде.

• Как правило, рекомендуется возбуждать события из метода, специально предназначенного для этой задачи, а не возбуждать события непосредственно в коде.
Шаг 5: Возбуждение события.
• Возбуждайте событие непосредственно в коде или вызывайте метод, возбуждающий событие.
• Перед возбуждением события вы должны создать экземпляр вашего подкласса EventArgs, заполненный связанными с событием данными. Если вы не используете никакой подкласс EventArgs, то вы должны добавить System.EventArgs.Empty вместо пользовательского класса EventArgs, когда вы возбуждаете метод.

12.2 Подготовка подписчика события

Так как эта статья дает образец события (object sender, EventArgs e), реализованного во всех классах среды разработки .NET, следующие шаги помогут вам подключить методы обработки события, работающие почти со всеми событиями среды разработки .NET, в дополнение к пользовательским событиям, которые вы создаете по тому же самому образцу.

Шаг 1: Написание метода обработки события.

• Определите метод обработки события с сигнатурой, точно совпадающей с сигнатурой делегата, на основе которого определяется метод.
• При использовании встроенного необобщенного делегата System.EventArgs или обобщенного делегата System.EventHandler в объявлении события, результирующая сигнатура автоматически соответствует соглашению возвращения void и принятия параметров (object sender, EventArgs e).

Шаг 2: Создание экземпляра публикатора события.

• Объявите переменную-член уровня класса, ссылающуюся на класс или объект, публикующий нужное событие.

Шаг 3: Создание экземпляра обработчика события (при необходимости).

• Если нужное событие основано на пользовательском обработчике события, создайте экземпляр этого обработчика события, передав имя метода обработки события.
• Этот шаг можно объединить с шагом 4 (следующим) путем использования ключевого слова new(новый) для создания экземпляра делегата в той же строке, в которой делегат регистрируется в событии.

Шаг 4: Регистрация подписчика (метода обработки события) в событии.

• Любая версия .NET: используйте синтаксис +=, чтобы зарегистрировать обработчик события в событии.
• .NET 2.0+: Как вариант, с помощью вывода делегата, вы можете непосредственно назначить событию имя метода.
• .NET 2.0+: Как вариант, если метод обработки события очень короткий (примерно 3 строки кода), вы можете сделать вашу реализацию более читабельной, зарегистрировав код обработки события с помощью «безымянного метода».

Шаг 5: Отмена регистрации подписчика (метода обработки события) в событии.

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

13. Реализация типового события

Этот пример использует пользовательский обработчик события, хранит данные события в пользовательском подклассе EventArgs, объявляет событие с использованием полеподобного синтаксиса, и иным образом соответствует рекомендуемым стандартам и принципам реализации события. Ни одна из возможностей .NET 2.0+ (безымянные методы и т.д.) не используются для сохранения максимальной ясности изложения.

Это типовое событие возбуждается, когда файл перемещается утилитой для перемещения файлов (в примере проекта). Данные события содержат (1) имя перемещенного файла, (2) путь к исходной папке, и (3) путь к файлу назначения.

13.1 Код публикатора типового события

Шаг 1: Производный класс от EventArgs

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

Шаг 2: Обработчик события (делегат)

Здесь мы объявляем новый делегат, соответствующий соглашениям события, возвращающий void и принимающий два параметра; object, имеющий имя ‘sender’, и EventArgs, имеющий имя ‘e’.

Шаг 3: Объявление события

Здесь мы объявляем событие с использованием полеподобного синтаксиса.

Шаг 4: Метод, возбуждающий событие

Здесь мы объявляем метод, возбуждающий событие.

Шаг 5: Возбуждение события

У нас есть метод, выполняющий нужную работу (перемещающий файл). Сразу после завершения работы вызывается метод, возбуждающий событие.

13.2 Код подписчика типового события

Шаг 1: Написание метода обработки события

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

Шаг 2: Создание экземпляра публикатора события

Шаг 3: Создание экземпляра обработчика события вместе с регистрацией подписчика (метода обработки события) в событии

Этот подход объединяет шаги 3 и 4 из шагов, перечисленных в разделе 14.2.

14. Обработка событий, вызванных компонентами среды разработки .NET – пошаговый разбор и пример

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

Этот раздел показывает, как среда разработки использует эти соглашения путем пошагового разбора реализации события Deleted(удалено) компонента FileSystemWatcher(«сторож файловой системы»). FileSystemWatcher – класс, предоставляемый в пространстве имен System.IO среды разработки .NET. Этот класс может использоваться для уведомления вашего приложения, когда конкретное место ввода/вывода на диск совершается в конкретной папке (например, создан новый файл, файл изменен или удален, и т.д.).

14.1 Реализация события ‘Deleted’ FileSystemWatcher

Класс System.IO.FileSystemWatcher предоставляет событие Deleted , которое возбуждается экземпляром FileSystemWatcher, когда он обнаруживает, что файл был удален в папке, за которой он следит.

Объявление события

Илон Маск рекомендует:  Строчные элементы

Так класс FileSystemWatcher среды разработки .NET объявляет свое событие Deleted:


Объявление делегата

Используется делегат FileSystemEventHandler, объявленный в среде разработки .NET так:

Заметьте, что делегат FileSystemEventHandler соответствует соглашению принятия двух параметров – первый, именуемый sender, имеет тип System.Object, и второй параметр, именуемый ‘e’, имеет тип System.EventArgs или тип его потомка. В этом случае заданный FileSystemEventArgs является потомком (как описано ниже).

Пользовательские EventArgs

Событие Deleted передает информацию об удаленном файле или каталоге с помощью подкласса System.EventArgs:

Класс FileSystemEventArgs расширяет System.EventArgs путем добавления следующих свойств:
• ChangedType — Получает тип произошедшего изменения каталога (передается как значение перечисления WatcherChangeType — создан, удален, изменен, и т.д.)
• FullPath – Получает полный путь к измененному файлу или каталогу
• Name – Получает имя измененного файла или каталога

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

Отметьте, что имя обработчика события, FileSystemEventHandler, не полностью соответствует соглашению об именовании, говорящему, что именем обработчика события должно быть имя события, за которым следует слово Handler. Помните, что соглашения – не законы или правила. Наоборот, они являются советами, как сделать ваш код более понятным. В случае класса FileSystemWatcher один обработчик события был реализован для поддержки нескольких событий, включая Deleted, Created и Changed – отсюда и незначительное нарушение строгой интерпретации соглашения об именовании. Строгое следование соглашению привело бы к созданию 3 одинаковых делегатов с разными именами (например, DeletedHandler, CreatedHandler, и т.д.). Или же можно было выбрать имя вроде такого DeletedOrCreatedOrChangedHandler, что было бы нелепо. В данном случае было выбрано разумное отступление от соглашения.

14.2 Обработка события FileSystemWatcher.Deleted

С учетом вышеуказанной реализации события, предоставленной средой разработки .NET, ниже приведен пример кода, который вы могли бы написать в вашем классе, который подписался бы на событие Deleted экземпляра FileSystemWatcher.

Этот код позволил бы вашему приложению реагировать на удаляемые файлы в каталоге C:\Temp.

15. События форм Windows

Формы Windows реализуют свои собственные события, а также обеспечивают обработку событий, возбуждаемых элементами управления, содержащимися внутри формы или внутри контейнерных элементов управления в классе формы. Кроме того, реализация и обработка событий в классах Windows Forms требуют тщательного анализа и даже применения особых шагов с учётом того, что Windows Forms и содержащиеся в них элементы управления проявляют «привязку к потоку» – то есть их свойства может обновлять только код, выполняющийся в том же потоке, который создал форму или элементы управления.

Эти и другие связанные с Windows Forms принципы представлены в данном разделе.

15.1 Различие NET 1.x и 2.0+ — частичные классы

Концепция «частичного класса» была введена в среду разработки .NET версии 2.0. Частичный класс – класс, объявленный с ключевым словом partial, и с частями класса, определенными в двух или более файлах исходного кода. Компилятор извлекает весь исходный код, определяющий частичный класс, из всех файлов, содержащих частичный класс, и выдает один [скомпилированный] класс. То есть частичный класс может располагаться в двух или более файлах исходного кода, но когда приложение компилируется, «части класса» собираются в один класс в выходной сборке.

Преимущества частичных классов включают (1) несколько разработчиков могут работать над разными частями одного и того же класса одновременно, работая с разными файлами исходного кода; и (2) автоматизированные инструменты генерации кода могут писать в один файл исходного кода, в то время как люди-разработчики могут поддерживать свой код в отдельном файле, не беспокоясь, что их изменения впоследствии могут быть переписаны автоматизированным инструментом генерации кода. Это второе преимущество реализовано в проектах Windows Forms, начиная с Visual Studio 2005. Когда вы добавляете новую форму в проект Windows Forms, Visual Studio автоматически создает форму в виде частичного класса, определенного в двух файлах исходного кода. Файл, содержащий код, сгенерированный Visual studio, называется FormName.Designer.cs, тогда как файл, предназначенный для кода разработчика, называется FormName.cs.

Например, если вы заставите Visual Studio 2005 создать форму с именем MainForm, то будут созданы следующие два файла исходного кода:

MainForm.cs – содержит определение частичного класса:

Когда вы добавляете элементы управления на форму путем использования проектировщика Windows Forms Visual Studio, проектировщик добавляет необходимый код в файл FormName.Designer.cs.

Разработчики не должны непосредственно изменять исходный код в файле FormName.Designer.cs, так как не исключено, что проектировщик перепишет такие изменения. Как правило, весь код разработчика должен быть записан в файле FormName.cs.

.NET 1.x не имеет частичных классов. Весь исходный код – будь то написанный Visual Studio или разработчиком – помещается в один файл исходного кода. Хотя проектировщик Windows Forms Visual Studio стремится писать код только в одном разделе этого файла, можно размещать код разработчика и сгенерированный код в одних и тех же разделах, при наличии вероятности, что проектировщик Windows Forms перепишет код, написанный разработчиком.

15.2 Частичные классы и принципы проектирования Windows Forms для событий

Когда Visual Studio 2005 создает для вас реализацию обработки события, код обработчика события/регистрации записывается в файл FormName.Designer.cs, причем только заглушка метода обработки события автоматически записывается в файл FormName.cs. Цель этой схемы в том, чтобы проектировщик Windows Forms писал весь связанный с событием код, который может быть автоматизирован (подключение метода обработки события к обработчику события и т.д.). Проектировщик не может создавать только специфическую логику программы, которая должна выполняться внутри метода обработки события. Когда Visual Studio заканчивает делать для вас все, что может, вы получаете (1) весь связанный с событием подключающий код, помещенный в файл FormName.Designer.cs; с (2) заглушкой метода обработки события, помещенной в файл FormName.cs. Вам остается только закончить реализацию обработки события, написав требуемый код в заглушке метода обработки события.

15.3 Пошаговый разбор – обработка события Windows Forms

Следующие шаги пошагово разбирают реализацию метода обработки события FormClosing в форме Windows с именем MainForm.
1. С использованием Visual Studio .NET создайте новый проект Windows Forms и добавьте новую форму с именем MainForm.
2. Открыв MainForm в режиме конструктора, щелкните правой кнопкой мыши по открытому участку формы (не по элементу управления), и выберите «Свойства» из всплывающего меню. Появится диалоговое окно «Свойства», отображающее свойства формы или события. Если это еще не выбрано, нажмите кнопку «События» (она имеет иконку светящейся молнии) на панели инструментов вверху диалогового окна свойства.
3. В диалоговом окне события найдите событие, на которое ваше приложение должно реагировать. В нашем случае это событие FormClosing. Дважды щелкните где-нибудь в строке, в которой указано FormClosing.

В этот момент происходят две вещи.

Первое – конструктор Windows Forms вставляет следующую строку в файл MainForm.Designer.cs.

Второе – конструктор Windows Forms вставляет следующую заглушку метода в файл MainForm.cs.
private void MainForm_FormClosing(object sender,

* При использовании .NET 1.x (не имеющей частичных классов), генерируется такой же код, но он помещается в один файл MainForm.cs.

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

Если вы хотите изменить имя метода обработки события, сгенерированного для вас конструктором Windows Forms, вы можете сделать это. Обязательно измените имя метода в файле MainForm.cs, и в том месте, где он регистрируется в обработчике события в MainForm.Designer.cs.

Событие FormClosing — это «предшествующее событие», являющееся отменяемым. Это означает, что событие возбуждается перед закрытием формы, и процедура обработки события может отменить событие, тем самым не дав закрыть форму.

В частности, отменяемым это событие делает параметр FormClosingEventArgs, имеющий тип класса, расширяющего System.ComponentModel.CancelEventArgs. Чтобы отменить событие FormClosing, установите булево свойство Cancel FormClosingEventArgs в true так:

15.4 Формы Windows и принципы поточной обработки

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

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

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

Есть несколько способов уменьшить эти проблемы поточной обработки:
• Установите свойство SynchronizingObject (при наличии и в соответствующих случаях)
• Используйте Control.InvokeRequired и Control.Invoke() для вызова кода, обновляющего пользовательский интерфейс.
• Разработчики компонентов могут использовать классы SynchronizationContext, AsyncOperation, и AsyncOperationManager.

SynchronizingObject (синхронизирующий объект)

Некоторые компоненты .NET предоставляют свойство SynchronizingObject. Примеры этих компонентов включают классы FileSystemWatcher, Timer и Process. Установка SynchronizingObject позволяет вызывать методы обработки события в том же потоке, который создал компонент пользовательского интерфейса, подлежащий обновлению. Например, событие Elapsed(прошло) таймера возбуждается из потока пула потоков. Когда SynchronizingObject компонент таймера установлен на компонент пользовательского интерфейса, метод обработки события для события Elapsed вызывается в том же потоке, в котором выполняется компонент пользовательского интерфейса. Затем компонент пользовательского интерфейса может быть обновлен из метода обработки события Elapsed.

Нужно отметить, что Visual Studio может автоматически устанавливать свойство SynchronizingObject на элемент управления, содержащий компонент. Следовательно, вы можете вообще не столкнуться с необходимостью явно устанавливать свойство SynchronizingObject.

Однако существуют сценарии, в которых может понадобиться явно установить свойство SynchronizingObject. Например, когда вы имеете библиотеку классов, экземпляр которой создается внутри Windows Form, и эта библиотека классов содержит экземпляр FileSystemWatcher. FileSystemWatcher порождает дополнительный фоновый поток, из которого возбуждаются его события. затем эти события обрабатываются внутри библиотеки классов. Пока все хорошо. Библиотека классов может обрабатывать события, так как у нее отсутствует привязка к потоку, свойственная элементам управления Windows Forms. Библиотека классов может, в ответ на получение события FileSystemWatcher, возбудить новое событие, которое затем обрабатывается в экземпляре, содержащем Windows Form. Возникнет следующее исключение, если SynchronizingObject не был установлен на эту форму (или на соответствующий элемент управления на ней), или код, обновляющий пользовательский интерфейс, не был вызван через Control.Invoke(), как описано далее.

Control.Invoke() и InvokeRequired

Есть два важных исключения из правила, гласящего, что Элементы управления Windows Forms не могут вызываться из потока, отличного от потока, в котором они были созданы. Все элементы управления наследуют метод Invoke() и свойство InvokeRequired, которые могут быть вызваны из других потоков. Invoke()принимает единственный аргумент, имеющий тип делегата. При вызове Invoke() заставляет делегат вызвать все методы, зарегистрированные в нем. Любой код, вызванный через Invoke(), будет выполнен в том же потоке, в котором находится элемент управления. Чтобы обновить элементы управления пользовательского интерфейса, выполняющиеся в одном потоке, из кода в другом потоке, просто (1) выделите код, обновляющий элемент управления пользовательского интерфейса, в отдельный метод; затем (2) зарегистрируйте этот метод в делегате, который затем (3) передайте в метод Invoke() элемента управления пользовательского интерфейса.

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

SynchronizationContext, AsyncOperation и AsyncOperationManager

Впервые появившиеся в версии 2.0 среды разработки .NET, эти классы предоставляют разработчикам компонентов, возбуждающим события асинхронно, еще одну возможность решить проблемы поточной обработки, описанные выше. Основное преимущество использования System.ComponentModel.AsyncOperation в том, что он обеспечивает решение проблем поточной обработки (описанных выше) в публикаторе события (компоненте), тогда как две представленных выше альтернативы (Control.Invoke и SynchronizingObject) дают решение для подписчиков.

16. Отменяемые события

Отменяемые события обычно возбуждаются компонентом, собирающимся выполнить некое действие, которое может быть отменено, или совершение которого может быть запрещено. Событие FormClosing класса Windows Form – пример отменяемого события. Типичный сценарий, в котором вам нужно запретить закрытие формы, происходит, когда пользователь не сохранил изменения. В этом сценарии ваш метод обработки события FormClosing может реализовать логику, обнаруживающую наличие несохраненных изменений. Если таковые имеются, то логика может попросить пользователя сохранить свои изменения. Если пользователь выберет сохранить свои изменения, ваша логика отменит событие FormClosing. Это предотвратит закрытие формы, тем самым дав пользователю возможность пересмотреть свои изменения и, вероятно, сохранить их перед повторной попыткой закрыть форму.

Внутренние механизмы отменяемого события могут быть весьма простыми. Учитывая, что события часто сообщают подписчикам, что изменение в состоянии или какое-то другое действие вскоре произойдет, это «предсобытие» дает публикатору события отличную возможность определить, должен ли публикатор позволять изменению в состоянии (или действию) совершиться. Если действию разрешено совершиться (т.е. ничто не говорит публикатору прервать операцию), то публикатор позволяет совершиться изменению в состоянии и впоследствии/дополнительно возбуждает постсобытие.
Итак, отменяемое событие – на самом деле два события и некоторое действие, совершающееся между этими событиями. «Предсобытие» возникает перед действием. После этого действие совершается (или нет). Если действие совершается, обычно возбуждается «постсобытие». Если быть точным, никакое событие не отменяется, несмотря на то, что мы говорим, что имеем отменяемое событие. Наоборот, отменяется действие, совершающееся между двумя событиями, — и, скорее всего, запуск этого действия вообще запрещается.

В поддержку вышеуказанного понятия отменяемого события среда разработки .NET предоставляет класс System.ComponentModel.CancelEventArgs, который можно использовать непосредственно или расширять для связанных с приложением целей. CancelEventArgs расширяет System.EventArgs путем предоставления булева свойства Cancel, которое, если установлено в true подписчиком события, используется публикатором события для отмены события. Код публикатора события создает экземпляр CancelEventArgs, который отправляется подписчикам, когда возбуждается предсобытие. По умолчанию, методы обработки события (в любых/всех подписчиках) запускаются одновременно. Следовательно, изменение состояния (или действие), о котором сообщает предсобытие, может совершиться только после завершения выполнения всех запущенных методов обработки события. Разумеется, публикатор события хранит свою ссылку на экземпляр CancelEventArgs после возбуждения события. Поэтому, если любые методы обработки события установят свойство Cancel в true, публикатор события увидит это до того, как попытается приступить к изменению состояния, и сможет отреагировать соответственно.

Последовательность действий может быть примерно такой:
1. Публикатор события создает экземпляр System.ComponentModel.CancelEventArgs (или его подкласса) с именем ‘e’
2. Затем метод возбуждения события возбуждает событие, передавая ‘e’ подписчикам события (возвращая значение Cancel в false)
3. Затем метод обработки события (в подписчике события) устанавливает значение e.Cancel в true, вероятно, спрашивая у пользователя
4. Затем метод возбуждения события получает значение e.Cancel и реагирует соответственно. В случае если e.Cancel = true, логика не даст совершиться изменению состояния или действию (например, закрытие формы).

В случае если событие имеет несколько подписчиков, событие будет отменено, если любой из методов обработки события установит e.Cancel = true. Точнее, публикатор события увидит, что e.Cancel = true, когда последний метод обработки события возвратит значение (они вызываются одновременно).

В конечном счёте, CancelEventArgs предоставляет подписчику события механизм передачи значения true | false публикатора события. Фактическая работа и смысл «отмены события» — полностью на ваше усмотрение, так как вы должны писать логику, реагирующую на значение e.Cancel.

Отмена долго выполняющейся операции

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

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

.NetBlog — блог о программировании на C# .Net, и других, не мене интересных вещах.

Заметки о разработке ПО, советы по .net, SharePoint, SQL, архитектура приложений, литература по программированию. А также просто просто мысли о жизни и рассказы о том, что интересно автору, например путешествиях и вкусной еде ;)

понедельник, 17 марта 2014 г.

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

Иногда бывает необходимо отлавливать события внутри приложения вне зависимости от того, какой контрол активен. Например, вы хотите реализовать аналог «IDDQD» в своем приложении ;))) Или просто хотите обрабатывать события по каким-то своим законам. Сегодня я расскажу вас как это сделать.

Для начала создадим класс, который наследует от IMessageFilter, и создать в нем события которые будут вызываться в процессе обработке. В моем случае — это для события: для нажатия клавиши на клавиатуре и прокрутки колесика мыши. Интерфейс IMessageFilter представляет только один член — метод PreFilterMessage в котором и происходит обработка очереди событий.
Да, и для каждого события нам нужен делегат описывающий сигнатуру вызова.

следующий этап — реализовать метод PreFilterMessage:

Внутри этого метода вы можете отлавливать какие угодно события. Вот тут их относительно полный список ( http://msdn.microsoft.com/en-us/library/ms644927%28v=VS.85%29.aspx#system_defined )
Ну и последний этап — нам надо написать методы обработчиков событий. Обратите внимание на строчку получения смещения из параметра delta. В сообщении WM_MOUSEWHEEL смещение прокрутки колеса находится в старшем слове параметра. Так, что, нам нужно сдвинуть его значение вправо и обрезать до short. Это я к тому, что внимательнее читайте описание сообщений

И, добавить ваш фильтр в очередь обработки сообщений приложения. Делать это нужно в Form_Load

События

Дата изменения: 07.10.2020

События C# представляют собой инструмент автоматического уведомления одного класса или объекта о произошедших изменениях в других.

Событие объявляется в классе (являются членами класса), в котором должно произойти событие, этот класс называется издателем. Класс, который информируется о произошедшем событии называется подписчиком.

Что такое события

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

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

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

Создание событий

События создаются в три этапа:

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

создание методов, которые будут вызваны при возникновении событий в классах подписчиках;

подписка на события, то есть связывание события и делегата (а соответственно и методов).

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

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

Для описания событий в .NET Framework описан класс EventArgs и делегат EventHandler.

Объявление события

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

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

Код легко сократить, если использовать встроенный тип делегата Action .

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

Теперь реализуем метод, который будет вызывать событие, дополнив класс соответствующим методом:

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

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

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

Для добавления и удаления ссылок на методы в события используются пара операторов: += и -=. Первый – добавляет, второй удаляет метод из таблицы ссылок в событии (делегате). Процедура добавления ссылки называется оформлением подписки, а удаления – удалением подписки.

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

Шаблон делегата событий

В предыдущих разделах, для хранения ссылки на метод обработки событий мы использовали один из шаблонных делегатов библиотеки типов .NET framework – Action . Однако, на практике гораздо чаще встречается применение шаблонного делегата событий EventHandler.

Вот его определение:

Как видно из определения EventHandler – void-делегат с двумя параметрами. Первый – типа object, ссылка на объект, который вызвал событие. Второй типа EventArgs хранит служебную информацию о конкретном событии.

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

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

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

Также необходимо создать класс-потомок EventArgs, который будет информировать обработчик события о событии:

Использование измененного класса:

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

Обработка событий в net с помощью c#

Чтобы генерировать объявленное событие, необходимо связать с ним один или не-
сколько обработчиков. Обработчик события — это метод, который вызывается по-
средством делегата всякий раз, когда генерируется соответствующее событие. Что-
бы этот механизм работал, необходимо связать обработчики с обрабатываемыми
событиями. Если в программе на Visual Basic .NET генерировать событие, с кото-
рым не связан ни один обработчик, попросту ничего не произойдет; в Visual C# же
подобные действия закончатся ошибкой.

C# Обработка события?

попробуете изменить тут
str1 += tbArray[i, j].Text

ps строка 78 исходника.. а вообще сам подход.. по меньшей мере странен.. TexBox это отображаемый контрол, но 100 текстбоксов — это откровенная несуразица. такой UI не жизнеспособен.. ну а если не нужны визуальные контролы — используйте строки.. или StrinBuilder, в зависимости от задачи

pps у всех потомков obj, есть метод ToString() .. но он возвращает тип объекта.. далеко не все потомки obj имеют поле Text, по этому, разработчики, вполне логично, не переопреоделяют данный метод, без явной необходимости

ppps наиболее очевидные поводы, переопределять ToString() у типов вроде Int32, Int64, Float, Double, в общем у value-типов.. но TextBox (повторюсь) визуальный контрол, если вы не планируете насиловать внимание пользователя, просмотром 100 (или более?) текстов.. то, тем не менеее, вы будете насиловать ресурсы компа )).. и ЦПУ, и ГПУ, на их отображение, или по меньшей мере, готовность к отображению. я думаю, вам стоит пересмотреть архитектуру своего приложения

pppps если вы используете заготовки чужего кода, обратите внимание, что TextBox.Text это: — во первых реальное хранилище текста, во вторых это банальный string. надеюсь эта подсказка поможет

ppppps дружите с MSDN.. благо кнопка F1, в абсолютно бесплатной студии VS 2020 Community очень в этом помогает

Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL