Делегат — это форма указателя на функцию с безопасным типом, используемая Common Language Infrastructure (CLI). Делегаты указывают метод для вызова и, опционально, объект для вызова метода. Делегаты используются, помимо прочего, для реализации обратных вызовов и прослушивателей событий . Объект делегата инкапсулирует ссылку на метод. Затем объект делегата может быть передан коду, который может вызвать указанный метод, без необходимости знать во время компиляции, какой метод будет вызван.
Многоадресный делегат — это делегат, который указывает на несколько методов. [1] [2] Многоадресное делегирование — это механизм, который обеспечивает функциональность для выполнения более одного метода. Существует список делегатов, поддерживаемый внутри, и когда вызывается многоадресный делегат, выполняется список делегатов.
В C# делегаты часто используются для реализации обратных вызовов в событийно-управляемом программировании. Например, делегат может использоваться для указания того, какой метод должен быть вызван, когда пользователь нажимает на какую-то кнопку. Делегаты позволяют программисту уведомлять несколько методов о том, что произошло событие. [3]
Код для объявления delegateтипа с именем SendMessageDelegate, который принимает Messageв качестве параметра и возвращает void:
делегат void SendMessageDelegate ( Сообщение сообщение );
Код для определения метода, который принимает в качестве аргумента инстанцированный делегат:
void SendMessage ( SendMessageDelegate sendMessageDelegateReference ) { // Вызвать делегата и любые другие связанные делегаты синхронно. sendMessageDelegateReference ( new Message ( "привет, это пример сообщения" )); }
Реализованный метод, который запускается при вызове делегата:
void HandleSendMessage ( Message message ) { // Реализация классов Sender и Message не имеет значения для этого примера. Sender . Send ( message ); }
Код для вызова метода SendMessage, передающий экземпляр делегата в качестве аргумента:
SendMessage ( new SendMessageDelegate ( HandleSendMessage ));
delegate void Notifier ( string sender ); // Обычная сигнатура метода с ключевым словом delegate УведомительприветствиеMe ; // Переменная делегата void HowAreYou ( string sender ) { Console . WriteLine ( "Как дела, " + sender + '?' ); } GreetingMe = новый уведомитель ( HowAreYou );
Переменная делегата вызывает связанный метод и вызывается следующим образом:
greetMe ( "Антон" ); // Вызывает HowAreYou("Антон") и печатает "Как дела, Антон?"
Переменные делегата являются объектами первого класса формы и могут быть назначены любому соответствующему методу или значению . Они хранят метод и его получатель без каких-либо параметров: [4]new DelegateType(obj.Method)
null
новый ТипДелегата ( funnyObj . HowAreYou );
Объект funnyObj
может быть this
и опущен. Если метод — static
, то это должен быть не объект (также называемый экземпляром в других языках), а сам класс. Он не должен быть abstract
, но может быть new
, override
или virtual
.
Для успешного вызова метода с делегатом сигнатура метода должна совпадать DelegateType
с с тем же количеством параметров того же вида ( ref
, out
, value
) с тем же типом (включая возвращаемый тип).
Переменная делегата может содержать несколько значений одновременно:
void HowAreYou ( string sender ) { Console . WriteLine ( $"Как дела, {sender}?" ); } void HowAreYouToday ( string sender ) { Console . WriteLine ( $"Как дела сегодня, {sender}?" ); } Уведомитель greetMe ; greetMe = КакТы ; greetMe += КакТыСегодня ; greetMe ( "Леонардо" ); // "Как дела, Леонардо?" // "Как дела сегодня, Леонардо?" greetMe -= Как дела ; greetMe ( "Перейра" ); // "Как дела сегодня, Перейра?"
Если многоадресный делегат является функцией или не имеет out
параметра, возвращается параметр последнего вызова. [5]
Хотя внутренние реализации могут различаться, экземпляры делегатов можно рассматривать как кортеж объекта , указателя метода и ссылки (возможно, нулевой) на другого делегата. Следовательно, ссылка на одного делегата может быть ссылкой на несколько делегатов. Когда первый делегат завершит работу, если его цепочка ссылок не нулевая, будет вызван следующий, и так далее, пока список не будет заполнен. Этот шаблон позволяет событию легко масштабировать накладные расходы от одной ссылки до отправки в список делегатов и широко используется в CLI.
Производительность делегатов раньше была намного ниже, чем у виртуальных или интерфейсных вызовов методов (в 6–8 раз медленнее в тестах Microsoft 2003 года) [6] , но с момента выхода .NET 2.0 CLR в 2005 году она примерно такая же, как у интерфейсных вызовов. [7] Это означает, что по сравнению с прямыми вызовами методов возникают небольшие дополнительные накладные расходы.
Существуют очень строгие правила построения классов делегатов. Эти правила предоставляют оптимизирующим компиляторам большую свободу действий при оптимизации делегатов, обеспечивая при этом безопасность типов. [ необходима цитата ]