Обзор делегатов

CLR предоставляет исполняющую систему, которая явно поддерживает гибкий механизм обратных вызовов. С самого начала времен – по крайней мере, со времен появления Windows – всегда существовала необходимость в функциях обратного вызова, которые система либо какая-то другая сущность вызовет в определенный момент времени, чтобы известить о чем-нибудь интересном. В конце концов, обратные вызовы являются удобным механизмом, посредством которого пользователи могут расширить функциональность компонента. Даже наиболее базовые компоненты приложения Win32 GUI – оконные процедуры – представляют собой функции обратного вызова, регистрируемые в системе. Система вызывает такую функцию всякий раз, когда нужно известить о поступлении некоторого события в окно. Этот механизм работает вполне хорошо в программной среде на базе языка С.

Все стало несколько сложнее с распространением объектно-ориентированных языков вроде С++. Разработчики немедленно захотели, чтобы система могла вызывать методы экземпляров на объектах, а не только глобальные функции или статические методы. Существует много решений этой проблемы. Но независимо от того, какое из них используется, в итоге все сводится к тому, что где-то кто-то должен хранить указатель на экземпляр объекта и вызывать метод экземпляра через этот указатель на него. Реализации обычно состоят из переходника (thunk), который представляет собой не что иное адаптер, такой как промежуточный блок данных или кода, вызывающий метод экземпляра через указатель на него. Переходник – в действительности функция, зарегистрированная в системе. За многие годы в С++ было разработано немало изобретательных реализаций переходников. Ваш доверчивый автор с сентиментальной нежностью может вспомнить множество примеров такого дизайна.

В настоящее время делегаты являются предпочтительным способом реализации обратных вызовов в CLR. Делегат имеет смысл представлять как хорошо известный указатель на функцию, в качестве которой может выступать статический метод или метод экземпляра. Экземпляр делегата – это в точности то же самое, что переходник, но в то же время он является полноправным гражданином в стране CLR. Фактически, при объявлении делегата в своем коде компилятор С# генерирует класс-наследник MulticastDelegate, и CLR реализует все интересные методы делегата динамически, во время выполнения. Вот почему вы не увидите никакого IL-кода, стоящего за методами делегата, если заглянете в скомпилированный модуль с помощью ILDASM.

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

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

Задумайтесь на минутку об элементах пользовательского интерфейса в диалоговом окне, таких как кнопка отправки, и о том, насколько много внешних сторон могут быть заинтересованы в знании факта выбора этой кнопки. Если класс, представляющий кнопку, должен напрямую вызывать эти заинтересованные стороны, ему нужно обладать подробными знаниями о компоновке этих заинтересованных сторон, или объектов, и знать, какие именно их методы должны быть вызваны. Ясно, что такое требование приводит к чрезмерной зависимости между кнопкой и заинтересованными сторонами, причем зависимость эта чрезвычайно сложна и превращает сопровождение кода в кошмар. Здесь на помощь приходят делегаты и разрывают эту связь. Теперь заинтересованные стороны должны только зарегистрировать делегат с кнопкой, и этот делегат предварительно сконфигурирован так, что может вызывать любые методы, которые им нужны. Этот механизм отделения описывает события, как поддерживаемые CLR. Дополнительные сведения о событиях CLR можно найти в разделе "События" далее в главе. А пока давайте посмотрим, как создаются и используются делегаты в С#.


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



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