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


Ključevoe slovo delegate i System.Delegate

Предыдущий

В этой статье рассматриваются классы в .NET, которые поддерживают делегатов и как они сопоставляются с ключевым словом delegate .

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

Начнем с ключевого слова "делегат", так как это в первую очередь то, что вы будете использовать при работе с делегатами. Код, создаваемый компилятором при использовании ключевого слова delegate, будет сопоставляться с вызовами методов, которые инициируют члены классов Delegate и MulticastDelegate.

Тип делегата определяется с помощью синтаксиса, аналогичного определению сигнатуры метода. Просто добавьте ключевое delegate слово в определение.

Давайте продолжим использовать метод List.Sort() в качестве примера. Первым шагом будет создать тип для делегата сравнения:

// From the .NET Core library

// Define the delegate type:
public delegate int Comparison<in T>(T left, T right);

Компилятор создает класс, производный от System.Delegate того, что соответствует используемой сигнатуре (в данном случае метод, возвращающий целое число и имеющий два аргумента). Тип этого делегата — Comparison. Тип Comparison делегата является универсальным типом. Дополнительные сведения см. в разделе "Универсальные классы и методы".

Обратите внимание, что синтаксис может отображаться так, как будто он объявляет переменную, но фактически объявляет тип. Можно определить типы делегатов внутри классов, непосредственно внутри пространств имен или даже в глобальном пространстве имен.

Замечание

Объявление типов делегатов (или других типов) непосредственно в глобальном пространстве имен не рекомендуется.

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

Объявление экземпляров для делегатов

После определения делегата можно создать экземпляр этого типа. Как и все переменные в C#, нельзя объявлять экземпляры делегатов как непосредственно в пространстве имен, так и в глобальном пространстве имен.

// inside a class definition:

// Declare an instance of that type:
public Comparison<T> comparator;

Тип переменной — Comparison<T>тип делегата, определенный ранее. Имя переменной — comparator.

Этот фрагмент кода выше объявил переменную-член внутри класса. Можно также объявить переменные делегата, которые являются локальными переменными или аргументами методов.

Вызов делегатов

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

int result = comparator(left, right);

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

Эта строка кода делает небезопасное предположение: нет никаких гарантий, что целевой объект был добавлен в делегат. Если целевые объекты не были присоединены, строка выше вызовет выброс исключения NullReferenceException. Идиомы, используемые для решения этой проблемы, более сложны, чем простая проверка null, и рассматриваются далее в этой серии.

Назначение, добавление и удаление целевых объектов вызова

Вот как определяется тип делегата и как объявляются и вызываются экземпляры делегатов.

Чтобы использовать метод List.Sort(), разработчикам необходимо определить метод, подпись которого соответствует определению типа делегата, и назначить его делегату, используемому методом сортировки. Это назначение добавляет метод в список вызовов этого объекта делегата.

Предположим, вы хотите отсортировать список строк по их длине. Функция сравнения может быть следующей:

private static int CompareLength(string left, string right) =>
    left.Length.CompareTo(right.Length);

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

Вы создаете эту связь, переправляя этот метод в метод List.Sort().

phrases.Sort(CompareLength);

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

Кроме того, можно было явно объявить переменную типа Comparison<string> и выполнить задание:

Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);

В использовании, где метод, используемый в качестве целевого объекта делегата, является небольшим методом, обычно используется синтаксис лямбда-выражения для выполнения задания:

Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);
phrases.Sort(comparer);

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

Пример Sort() обычно присоединяет к делегату один целевой метод. Однако объекты делегатов поддерживают списки вызовов с несколькими целевыми методами, подключенными к объекту делегата.

Классы Делегат и MulticastDelegate

Поддержка языка, описанная выше, предоставляет функции и поддержку, которые обычно требуются для работы с делегатами. Эти функции основаны на двух классах в платформе .NET Core: Delegate и MulticastDelegate.

Класс System.Delegate и его отдельный подкласс System.MulticastDelegate, обеспечивают поддержку платформы для создания делегатов, регистрации методов в качестве целевых объектов делегатов и вызова всех методов, зарегистрированных в качестве целевого объекта делегата.

Интересно, что классы System.Delegate и System.MulticastDelegate сами по себе не являются типами делегатов. Они предоставляют основу для всех конкретных типов делегатов. Тот же процесс проектирования языка запрещает объявлять класс, производный от Delegate или MulticastDelegate. Правила языка C# запрещают его.

Вместо этого, когда вы используете ключевое слово языка C# для объявления типов делегатов, компилятор C# создает экземпляры класса, производного от MulticastDelegate.

Эта конструкция имеет свои корни в первом выпуске C# и .NET. Одной из целей дизайнерской команды было гарантировать безопасность типов на языке при использовании делегатов. Это означало необходимость гарантировать, что делегаты вызываются с правильным типом и числом аргументов. И, что любой тип возвращаемого значения правильно указан во время компиляции. Делегаты были частью выпуска 1.0 .NET, который произошел до появления дженериков.

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

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

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

Методы, которые вы будете использовать чаще всего с делегатами, это Invoke() и BeginInvoke() / EndInvoke(). Invoke() вызовет все методы, присоединенные к конкретному объекту делегата. Как вы видели выше, делегаты обычно вызываются с помощью синтаксиса вызова метода через переменную делегата. Как вы увидите далее в этой серии, существуют шаблоны, которые работают непосредственно с этими методами.

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

Далее