Speak.Me Учить иностранные слова

C#: делегаты (Delegates)

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

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

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

Возвращаемый тип — значимый тип, возвращаемый методами, на который может ссылаться делегат. Имя — имя делегата. Список параметров — параметры, принимаемые методами, на которые может ссылаться делегат:

Экземпляр делегата

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

Инициализацию делегата можно записать короче:

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

Инструкция t(3) является сокращением для:

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

Делегат может использоваться для вызова как статических методов, так и методов экземпляра.

Многоадресатная передача (multicasting)

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

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

Можно также использовать операторы + и =:

Теперь при вызове делегата d будут последовательно вызваны методы SomeMethod1 и SomeMethod2. Методы вызываются в той последовательности, в которой они добавлялись.

Удалить метод из цепочки вызовов можно с помощью оператора -= или операторов - и =:

Теперь при вызове d будет вызван только метод SomeMethod2.

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

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

Если делегат с многоадресатной передачей возвращает какое либо значение (а не void), то вызывающий код принимает только значение возвращенное последним методом в цепочке вызовов. Значения, возвращаемые предшествующими методами, отбрасываются. В связи с этим как правило используются делегаты с многоадресатной передачей не возвращающие значения (возвращающие тип void), а для возврата значений обычно используется передача параметров по ссылке с помощью модификаторов ref или out. Соответственно методы, которые будет вызывать делегат, также должны возвращать void и принимать параметры по ссылке.

Все типы делегаты неявно наследуются от System.MulticastDelegate, который в свою очередь наследуется от System.Delegate. Класс System.Delegate имеет статические методы Combine, добавляющий метод в делегат, и Remove, удаляющий метод из делегата. Результат использования этих методов такой же как от использования операторов += и -=.

Подключаемые методы (Plug-in Methods)

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

Обобщенные делегаты (Generic Delegate Types)

Делегаты могут принимать обобщенные параметры типа:

Делегаты Func и Action

В силу того что делегаты могут быть обобщенными, появляется возможность объявить универсальные типы делегаты, способные принимать любые методы: возвращающие значения любых типов и принимающие любое количество аргументов любого типа. И такие делегаты уже определены в пространстве имен System — это делегаты Func и Action:

Эти делегаты абсолютно универсальны. Делегат Transformer из примера выше вполне можно заменить на Func:

Единственные случаи, которые не охватываются делегатами Func и Action, это передача параметров по ссылке, с модификаторами ref и out.

Совместимость делегатов

Разные типы делегаты абсолютно не совместимы, даже если их сигнатуры совпадают:

Однако делегат можно передать в другой делегат как метод:

Экземпляры делегатов считаются одинаковыми если они имеют один и тот же типы и ссылаются на один и тот же метод. Для многоадресатных делегатов также важна последовательность методов.

Ковариантность делегатов

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

Контравариантность делегатов

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

Параметры типа обобщенных делегатов

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

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

Параметр типа, передаваемый с модификатором in и используемый как тип входящего параметра, будет контравариантом и позволит делать следующее: