Поделиться через


Использование делегатов (руководство по программированию на C#)

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

public delegate void Callback(string message);

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

// Create a method for a delegate.
public static void DelegateMethod(string message)
{
    Console.WriteLine(message);
}
// Instantiate the delegate.
Callback handler = DelegateMethod;

// Call the delegate.
handler("Hello World");

Типы делегатов являются производными от Delegate класса в .NET. Типы делегатов запечатанные, они не могут быть производными от, и невозможно наследовать пользовательские классы от Delegate. Так как экземпляр делегата является объектом, его можно передать в качестве аргумента или присвоить свойству. Метод может принять делегат в качестве параметра и вызвать его в более позднее время. Это называется асинхронным обратным вызовом и является общим методом уведомления вызывающего объекта при завершении длительного процесса. Если делегат используется таким образом, код, использующий делегат, не нуждается в знании о реализации используемого метода. Функциональность аналогична той, которую обеспечивают интерфейсы инкапсуляции.

Другое частое использование обратных вызовов — определение пользовательского метода сравнения и передача делегата в метод сортировки. Он позволяет вызывающему коду стать частью алгоритма сортировки. В следующем примере метода в качестве параметра используется Del тип:

public static void MethodWithCallback(int param1, int param2, Callback callback)
{
    callback("The number is: " + (param1 + param2).ToString());
}

Затем делегат, созданный в предыдущем примере, можно передать в этот метод:

MethodWithCallback(1, 2, handler);

И получите следующие выходные данные в консоли:

The number is: 3

MethodWithCallback не требуется вызывать консоль напрямую— ее не нужно разрабатывать с учетом консоли. Что делает MethodWithCallback — это подготавливает строку и передаёт её другому методу. Делегированный метод может использовать любое количество параметров.

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

public class MethodClass
{
    public void Method1(string message) { }
    public void Method2(string message) { }
}

Наряду со статическим DelegateMethod, показанным ранее, теперь у нас есть три метода, которые можно обернуть в экземпляр Del.

Делегат может вызывать более одного метода при активации, что называется мультикастингом. Чтобы добавить дополнительный метод в список методов делегата — список вызовов, просто требуется добавить два делегата с помощью операторов добавления или добавления ("+" или "+="). Рассмотрим пример.

var obj = new MethodClass();
Callback d1 = obj.Method1;
Callback d2 = obj.Method2;
Callback d3 = DelegateMethod;

//Both types of assignment are valid.
Callback allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

allMethodsDelegate содержит три метода в списке вызовов: Method1, Method2, и DelegateMethod. Исходные три делегата, d1d2, и d3, остаются неизменными. При вызове allMethodsDelegate все три метода вызываются в упорядоченном виде. Если делегат использует ссылочные параметры, ссылка передается последовательно каждому из трех методов, и любые изменения по одному методу видны следующему методу. Если любой метод выбрасывает исключение, которое не поймано в методе, это исключение передается вызывающей стороне делегата. В списке вызовов не вызываются последующие методы. Если делегат имеет возвращаемое значение и/или параметры выхода, возвращает возвращаемое значение и параметры последнего вызываемого метода. Чтобы удалить метод из списка вызовов, используйте операторы вычитания или присваивания вычитания (- или -=). Рассмотрим пример.

//remove Method1
allMethodsDelegate -= d1;

// copy AllMethodsDelegate while removing d2
Callback oneMethodDelegate = (allMethodsDelegate - d2)!;

Так как типы делегатов являются производными от System.Delegate, методы и свойства, определенные тем классом, можно вызвать на делегате. Например, чтобы найти количество методов в списке вызовов делегата, можно написать следующее:

int invocationCount = d1.GetInvocationList().GetLength(0);

Делегаты с несколькими методами в списке вызовов происходят от MulticastDelegate, который является суперклассом System.Delegate. Предыдущий код работает в любом случае, так как оба класса поддерживают GetInvocationList.

Делегаты многоадресной рассылки широко используются в обработке событий. Объекты источника событий отправляют уведомления о событиях получателям, зарегистрированным для получения этого события. Чтобы зарегистрировать событие, получатель создает метод, предназначенный для обработки события, а затем создает делегат для этого метода и передает этот делегат источнику данного события. Источник вызывает делегата при возникновении события. Затем делегат вызывает метод обработки событий у получателя, передавая данные события. Источник событий определяет тип делегата для данного события. Дополнительные сведения см. в разделе "События".

Сравнение делегатов двух разных типов, назначенных во время компиляции, приводит к ошибке компиляции. Если экземпляры делегата статически относятся к типу System.Delegate, то сравнение допускается, но возвращает значение false во время выполнения. Рассмотрим пример.

delegate void Callback1();
delegate void Callback2();

static void method(Callback1 d, Callback2 e, System.Delegate f)
{
    // Compile-time error.
    Console.WriteLine(d == e);

    // OK at compile-time. False if the run-time type of f
    // is not the same as that of d.
    Console.WriteLine(d == f);
}

См. также