Содержание
Делегат — это объект, который может ссылаться на метод. При этом метод, вызываемый делегатом определяется не во время компиляции, а во время выполнения программы. В силу этого при выполнении программы один и тот же делегат можно использовать для вызова различных методов, просто заменив метод, на который ссылается этот делегат.
Объявление типа делегата
Делегат определяется как тип с помощью ключевого слова 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; | 

