Содержание
Делегат — это объект, который может ссылаться на метод. При этом метод, вызываемый делегатом определяется не во время компиляции, а во время выполнения программы. В силу этого при выполнении программы один и тот же делегат можно использовать для вызова различных методов, просто заменив метод, на который ссылается этот делегат.
Объявление типа делегата
Делегат определяется как тип с помощью ключевого слова delegate
. При объявлении типа делегата нужно указать тип, возвращаемый методами, на который будет ссылаться делегат, и список параметров, которые эти методы будут принимать. Объявление делегата похоже на декларирование абстрактного метода. Общая форма объявления делегата такая:
1 | delegate возвращаемый_тип имя (список_параметров); |
Возвращаемый тип — значимый тип, возвращаемый методами, на который может ссылаться делегат. Имя — имя делегата. Список параметров — параметры, принимаемые методами, на которые может ссылаться делегат:
1 | delegate int Transformer (int x); |
Экземпляр делегата
Чтобы использовать делегат нужно создать экземпляр делегата — объект, который и будет ссылать на один или несколько методов. При создании экземпляра делегата ему в качестве параметра передается имя (только имя, без параметров) метода, на который он ссылается:
1 | Transformer t = new Transformer (Square); |
Инициализацию делегата можно записать короче:
1 | Transformer t = Square; |
После создания экземпляра делегата, он может быть вызван как обычный метод:
1 2 3 4 5 6 7 8 9 10 | class Test { static void Main() { Transformer t = Square; // Создание экземпляра делегата int result = t(3); // Вызов делегата Console.Write (result); } static int Square (int x) { return x * x; } } |
Инструкция t(3)
является сокращением для:
1 | t.Invoke (3); |
В процессе выполнения программы можно заменить метод, на который ссылается делегат, но при этом делегат может вызывать только те методы, у которых возвращаемый тип и список параметров совпадают с его собственными.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Test { static void Main() { Transformer t = Square; int result = t(3); Console.Write (result); // 9 t = Increment; int anotherResult = t(3); Console.Write (anotherResult ); // 4 } static int Square (int x) { return x * x; } static int Increment (int x) { return ++x; } } |
Делегат может использоваться для вызова как статических методов, так и методов экземпляра.
Многоадресатная передача (multicasting)
Делегаты поддерживают многоадресатную передачу. Многоадресатная передача — это способность создавать список вызовов (или цепочку вызовов) методов, которые должны автоматически вызываться при вызове делегата.
Для реализации многоадресатной передачи необходимо создать экземпляр делегата, а затем добавить ему новый метод с помощью оператора +=
:
1 2 | SomeDelegate d = SomeMethod1; // или SomeDelegate d = new SomeDelegate (SomeMethod1) d += SomeMethod2; // или d += new SomeDelegate (SomeMethod2) |
Можно также использовать операторы + и =:
1 | d = d + SomeMethod2; |
Теперь при вызове делегата d
будут последовательно вызваны методы SomeMethod1
и SomeMethod2
. Методы вызываются в той последовательности, в которой они добавлялись.
Удалить метод из цепочки вызовов можно с помощью оператора -=
или операторов -
и =
:
1 | d -= SomeMethod1; |
Теперь при вызове d
будет вызван только метод SomeMethod2
.
Добавлять методы с помощью операторов +=
и +
, =
можно и в пустой экземпляр делегата имеющий значение null
(т.е. в который еще не ссылается ни на один метод). Также можно удалять последний метод из делегата с помощью операторов -=
и -
, =
, в результате чего делегат будет иметь значение null
.
Делегаты неизменны, поэтому добавляя и удаляя из делегата методы с помощью операторов +=
и -=
по сути создается новый экземпляр делегата и присваивается существующей переменной.
Если делегат с многоадресатной передачей возвращает какое либо значение (а не void
), то вызывающий код принимает только значение возвращенное последним методом в цепочке вызовов. Значения, возвращаемые предшествующими методами, отбрасываются. В связи с этим как правило используются делегаты с многоадресатной передачей не возвращающие значения (возвращающие тип void
), а для возврата значений обычно используется передача параметров по ссылке с помощью модификаторов ref
или out
. Соответственно методы, которые будет вызывать делегат, также должны возвращать void
и принимать параметры по ссылке.
Все типы делегаты неявно наследуются от System.MulticastDelegate
, который в свою очередь наследуется от System.Delegate
. Класс System.Delegate
имеет статические методы Combine
, добавляющий метод в делегат, и Remove
, удаляющий метод из делегата. Результат использования этих методов такой же как от использования операторов +=
и -=
.
Подключаемые методы (Plug-in Methods)
Делегат (экземпляр делегата) может быть передан в метод в качестве параметра. Метод (методы), на который ссылается такой делегат, будет являться подключаемым методом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public delegate int Transformer (int x); class Test { static void Main() { int[] values = { 1, 2, 3 }; Transform (values, Square); foreach (int i in values) Console.Write (i + " "); // 1 4 9 } static void Transform (int[] values, Transformer t) { for (int i = 0; i < values.Length; i++) values[i] = t (values[i]); } static int Square (int x) { return x * x; } } |
Обобщенные делегаты (Generic Delegate Types)
Делегаты могут принимать обобщенные параметры типа:
1 2 3 4 5 6 7 | public delegate T Transformer<T> (T arg); static double Square (double x) { return x * x; } static void Main() { Transformer<double> s = Square; Console.WriteLine (s (3.3)); // 10.89 } |
Делегаты Func и Action
В силу того что делегаты могут быть обобщенными, появляется возможность объявить универсальные типы делегаты, способные принимать любые методы: возвращающие значения любых типов и принимающие любое количество аргументов любого типа. И такие делегаты уже определены в пространстве имен System
— это делегаты Func
и Action
:
1 2 3 4 5 6 7 8 | delegate TResult Func <out TResult> (); delegate TResult Func <in T, out TResult> (T arg); delegate TResult Func <in T1, in T2, out TResult> (T1 arg1, T2 arg2); //... и так далее до T16 delegate void Action (); delegate void Action <in T> (T arg); delegate void Action <in T1, in T2> (T1 arg1, T2 arg2); //... и так далее до T16 |
Эти делегаты абсолютно универсальны. Делегат Transformer
из примера выше вполне можно заменить на Func
:
1 2 3 4 5 | public static void Transform<T> (T[] values, Func<T,T> transformer) { for (int i = 0; i < values.Length; i++) values[i] = transformer (values[i]); } |
Единственные случаи, которые не охватываются делегатами Func
и Action
, это передача параметров по ссылке, с модификаторами ref
и out
.
Совместимость делегатов
Разные типы делегаты абсолютно не совместимы, даже если их сигнатуры совпадают:
1 2 3 4 | delegate void D1(); delegate void D2(); D1 d1 = Method1; D2 d2 = d1; // Ошибка |
Однако делегат можно передать в другой делегат как метод:
1 | D2 d2 = new D2 (d1); |
Экземпляры делегатов считаются одинаковыми если они имеют один и тот же типы и ссылаются на один и тот же метод. Для многоадресатных делегатов также важна последовательность методов.
Ковариантность делегатов
Любой метод может вернуть значение более специфичного типа, чем предусмотрено при объявлении метода. В этом как раз и состоит суть полиморфизма. В соответствии с этим метод, на который ссылается делегат, может вернуть значение более специфичного типа, чем предусмотрен при объявлении делегата. В этом состоит ковариантность делегатов: возвращаемый делегатами тип ковариантен.
1 2 3 4 5 6 7 8 9 | delegate object ObjectRetriever(); // ... static void Main() { ObjectRetriever o = new ObjectRetriever (GetString); object result = o(); Console.WriteLine (result); // hello } static string GetString() { return "hello"; } |
Контравариантность делегатов
Аналогично сказанному выше, любой метод, реализую принцип полиморфизма, может принимать аргументы более специфичного типа, чем предусмотренный при объявлении тип параметров. В соответствии с этим метод, на который ссылается делегат, может принимать параметры более общего типа, чем предусмотрены при объявлении делегата. В этом состоит контравариантность делегатов.
1 2 3 4 5 6 7 8 9 10 11 | delegate void StringAction (string s); // ... static void Main() { StringAction sa = new StringAction (ActOnObject); sa ("hello"); } static void ActOnObject (object o) { Console.WriteLine (o); // hello } |
Параметры типа обобщенных делегатов
Ковариантность и контравариантность параметров типа, о которой говорилось при обсуждении обобщенных типов, полностью применима к обобщенным делегатам.
В соответствии с этим параметр типа, передаваемый с модификатором out и используемый в качестве типа возвращаемого значения, будет ковариантом и позволит делать следующее:
1 2 3 | delegate TResult Func<out TResult>(); Func<string> x = ...; Func<object> y = x; |
1 2 3 | delegate void Action<in T> (T arg); Action<object> x = ...; Action<string> y = x; |