Делегаты и события — мощные инструменты в C#, но поначалу они определенно могут сбивать с толку. Сегодняшняя статья охватывает основы этой темы и поможет вам с удобством реализовать этот подход в ваших собственных приложениях.
Делегаты
Я думаю, основная проблема в том, что делегаты часто объясняются излишне сложно. По этой причине я хочу дать вам легко понятное введение. Давайте посмотрим на базовое объявление делегата:
Объявление делегата очень похоже на объявление метода. В этом случае делегат выглядит как метод без возвращаемого значения, но с двумя параметрами, каждый из которых имеет строковый тип. Единственное отличие заключается в ключевом слове «делегат» и отсутствующем теле метода. Представьте делегатов не как методы, а как ссылки на методы.
Рассмотрим следующие два метода:
Нетрудно понять, что делают эти два метода. Однако обратите внимание на сигнатуру обоих методов: она идентична сигнатуре ранее определенного делегата. Это означает, что делегат можно использовать в качестве ссылки для методов. Единственным решающим фактором здесь является тип соответствующего параметра, а не имя.
Для создания этой ссылки создается новый экземпляр делегата. Компилятор создает полноценный класс из делегата, поэтому создание экземпляра можно выполнить как обычно с помощью ключевого слова new. В качестве аргумента делегат получает имя нужного метода. Упомянутый метод теперь можно вызывать через созданный экземпляр делегата.
Многоадресные делегаты
Сильная особенность делегатов, так называемая многоадресная рассылка, позволяет создавать цепочки вызовов методов. Проиллюстрируем это на примере:
Это выполнит сохраненные методы для del1 и del2, используя аргументы «Hello» и «World» для обоих методов. Это основано на так называемом списке вызовов. У каждого делегата есть такой список, который содержит ссылки на все указанные методы, которые затем вызываются при вызове делегата.
Вы можете получить доступ к списку вызовов делегата, используя метод GetInvocationList. В этом примере вы увидите, что del1 содержит две записи. Записи в InvocationList всегда создаются в сохраненной последовательности.
В заключение этой главы я хотел бы показать вам способ изменения аргументов при работе со списком вызовов. Делегаты также могут иметь возвращаемые значения, но это бесполезно на этапах обработки. При использовании связанных делегатов с возвращаемыми типами будет возвращен результат последнего элемента из списка вызовов.
Ключевым моментом является использование ссылочных типов вместо типов значений. При этом в метод передается ссылка, а не копия переменной. Для этого параметры должны быть помечены ключевым словом ref. Я изменил содержимое своего метода SayHello(), чтобы продемонстрировать все это. После вывода текста на консоль метод удаляет все гласные из переменной yourName.
События
События указывают на возникновение действия. Представьте событие как уведомление для всех заинтересованных сторон. Хитрость здесь в том, что рейзер события не должен знать о сторонах, которые в этом заинтересованы (подписчики). Когда происходит событие, данные могут быть дополнительно отправлены. В C# это делается с помощью класса EventArgs или производного от него класса.
События в основном просто инкапсулируют делегатов. Это, пожалуй, тот пункт, на который у меня ушло больше всего времени. Я собираюсь проиллюстрировать все это на примере. С нашими текущими знаниями мы можем создать следующее приложение:
Я думаю, вы тоже быстро поймете, что здесь происходит. Приложение запускает и создает рабочий объект. Вызывается рабочий метод, который трижды ждет по одной секунде, а затем выводит некоторую информацию. Наконец, вызывается Delegate WorkCompletedHandler, которому передается отработанное время. Теперь приложение отображает окно сообщения и уведомляет пользователя о том, что метод работы выполнен. Единственное, что здесь нужно помнить, — это привязка метода OnWorkerCompleted к списку вызовов делегата.
Давайте посмотрим, как это выглядит при использовании события:
Выглядит почти так же, правда? Как я уже говорил, события — это всего лишь оболочки для делегатов. Так что теоретически вы можете использовать обе представленные мной версии. Однако события используются по той же причине, что и свойства: прямое раскрытие полей считается плохой практикой. Таким образом, в соответствии с принципом инкапсуляции они изолированы от внешнего мира. Точно так же и здесь. События используются для инкапсуляции доступа к делегатам.
Последнее, чем я хочу поделиться с вами в этой статье, — это использование пользовательских EventArgs. Платформа .NET имеет соглашение, в соответствии с которым тип делегата, используемый для события, всегда принимает два параметра. Первый — это ссылка на объект, вызывающий событие. Другой является объектом класса EventArgs или наследуется от него. Мы хотим создать собственное наследование EventArgs, содержащее отработанные часы. Следующий код делает это за нас:
Заворачивать
Делегаты — это ссылки на методы. Компилятор создает класс при генерации удаления, затем экземпляры делегата создаются как обычно с новым ключевым словом. Как и методы, делегаты могут иметь возвращаемый тип. Используя многоадресную рассылку, делегаты могут быть объединены в цепочку, порядок вызовов основан на списке вызовов делегатов. События используются для инкапсуляции делегатов. События обычно используют два параметра, первый из которых является ссылкой на триггер, а второй — экземпляром EventArgs или его наследующим классом.
Первоначально опубликовано на blog.mjurtz.com.