Делегаты

60

Делегат представляет собой объект, который может ссылаться на метод. Следовательно, когда создается делегат, то в итоге получается объект, содержащий ссылку на метод. Более того, метод можно вызывать по этой ссылке. Иными словами, делегат позволяет вызывать метод, на который он ссылается.

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

Как только делегат создан и снабжен необходимой информацией, он может динамически вызывать методы, на которые указывает, во время выполнения. Каждый делегат в .NET Framework (включая специальные делегаты) автоматически снабжается способностью вызывать свои методы синхронно или асинхронно. Этот факт значительно упрощает задачи программирования, поскольку позволяет вызывать метод во вторичном потоке выполнения без ручного создания и управления объектом Thread.

Определение типа делегата в C#

Тип делегата объявляется с помощью ключевого слова delegate. Ниже приведена общая форма объявления делегата:

delegate возвращаемый_тип имя (список_параметров);

где возвращаемый_тип обозначает тип значения, возвращаемого методами, которые будут вызываться делегатом; имя — конкретное имя делегата; список_параметров — параметры, необходимые для методов, вызываемых делегатом. Как только будет создан экземпляр делегата, он может вызывать и ссылаться на те методы, возвращаемый тип и параметры которых соответствуют указанным в объявлении делегата.

Самое главное, что делегат может служить для вызова любого метода с соответствующей сигнатурой и возвращаемым типом. Более того, вызываемый метод может быть методом экземпляра, связанным с отдельным объектом, или же статическим методом, связанным с конкретным классом. Значение имеет лишь одно: возвращаемый тип и сигнатура метода должны быть согласованы с теми, которые указаны в объявлении делегата.

Давайте рассмотрим пример:

using System;

namespace ConsoleApplication1
{
    // Создадим делегат
    delegate int IntOperation (int i, int j);

    class Program
    {
        // Организуем ряд методов
        static int Sum(int x, int y)
        {
            return x + y;
        }

        static int Prz(int x, int y)
        {
            return x * y;
        }

        static int Del(int x, int y)
        {
            return x / y;
        }

        static void Main()
        {
            // Сконструируем делегат
            IntOperation op1 = new IntOperation(Sum);

            int result = op1(5, 10);
            Console.WriteLine("Сумма: " + result);

            // Изменим ссылку на метод
            op1 = new IntOperation(Prz);
            result = op1(5, 10);
            Console.WriteLine("Произведение: " + result);

            Console.ReadLine();
        }
    }
}
Создание делегатов

Главный вывод из данного примера заключается в следующем: в тот момент, когда происходит обращение к экземпляру делегата IntOperation, вызывается метод, на который он ссылается. Следовательно, вызов метода разрешается во время выполнения, а не в процессе компиляции.

Базовые классы System.MulticastDelegate и System.Delegate

Когда компилятор C# обрабатывает тип делегата, он автоматически генерирует запечатанный (sealed) класс, унаследованный от System.MulticastDelegate. Этот класс (в сочетании с его базовым классом System.Delegate) предоставляет необходимую инфраструктуру для делегата, чтобы хранить список методов, подлежащих вызову в более позднее время. Например, если просмотреть делегат IntOperation из предыдущего примера с помощью утилиты ildasm.exe, обнаружится класс, показанный на рисунке:

Как видите, сгенерированный компилятором класс IntOperation определяет три общедоступных метода. Invoke() — возможно, главный из них, поскольку он используется для синхронного вызова каждого из методов, поддерживаемых объектом делегата; это означает, что вызывающий код должен ожидать завершения вызова, прежде чем продолжить свою работу. Может показаться странным, что синхронный метод Invoke() не должен вызываться явно в коде C#. Invoke() вызывается "за кулисами", когда применяется соответствующий синтаксис C#.

Методы BeginInvoke() и EndInvoke() предлагают возможность вызова текущего метода асинхронным образом, в отдельном потоке выполнения. Имеющим опыт в многопоточной разработке должно быть известно, что одной из основных причин, вынуждающих разработчиков создавать вторичные потоки выполнения, является необходимость вызова методов, которые требуют определенного времени для завершения. Хотя в библиотеках базовых классов .NET предусмотрено целое пространство имен, посвященное многопоточному программированию (System.Threading), делегаты предлагают эту функциональность в готовом виде.

Ниже показаны некоторые избранные методы System.MulticastDelegate:

public abstract class MulticastDelegate : Delegate
{
  // Возвращает список методов, на которые "указывает" делегат.
  public sealed override Delegate[] GetlnvocationList ();
  // Перегруженные операции.
  public static bool operator ==(MulticastDelegate d1, MulticastDelegate d2) ;
  public static bool operator !=(MulticastDelegate d1, MulticastDelegate d2) ;
  // Используются внутренне для управления списком методов, поддерживаемых делегатом.
  private IntPtr _invocationCount;
  private object _invocationList;
}

Класс System.MulticastDelegate получает дополнительную функциональность от своего родительского класса System.Delegate. Ниже показан фрагмент определения класса:

public abstract class Delegate : ICloneable, ISerializable
{
  // Методы Для взаимодействия со списком функций.
  public static Delegate Combine(params Delegate[] delegates);
  public static Delegate Combine(Delegate a, Delegate b) ;
  public static Delegate Remove(Delegate source, Delegate value);
  public static Delegate RemoveAll(Delegate source, Delegate value);
  // Перегруженные операции.
  public static bool operator ==(Delegate d1, Delegate d2) ;
  public static bool operator != ( Delegate d1, Delegate d2) ;
  // Свойства, показывающие цель делегата.
  public Methodlnfo Method { get; }
  public object Target { get; }
}

Запомните, что вы никогда не сможете напрямую наследовать от этих базовых классов в коде (при попытке сделать это выдается ошибка компиляции). Тем не менее, при использовании ключевого слова delegate неявно создается класс, который "является" MulticastDelegate. В следующей таблице описаны основные члены, общие для всех типов делегатов:

Методы и свойства делегатов C#
Член Назначение
Method Это свойство возвращает объект System.Reflection.Method, который представляет детали статического метода, поддерживаемого делегатом
Target Если метод, подлежащий вызову, определен на уровне объекта (т.е. не является статическим), то Target возвращает объект, представляющий метод, поддерживаемый делегатом. Если возвращенное Target значение равно null, значит, подлежащий вызову метод является статическим
Combine() Этот статический метод добавляет метод в список, поддерживаемый делегатом. В C# этот метод вызывается за счет использования перегруженной операции += в качестве сокращенной нотации
GetlnvokationList() Этот метод возвращает массив типов System.Delegate, каждый из которых представляет определенный метод, доступный для вызова
Remove()
RemoveAll()
Эти статические методы удаляют метод (или все методы) из списка вызовов делегата. В C# метод Remove () может быть вызван неявно, посредством перегруженной операции -=
Пройди тесты
Лучший чат для C# программистов