Чтение онлайн

ЖАНРЫ

Язык программирования C#9 и платформа .NET5
Шрифт:

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

Глава завершается исследованием лямбда-выражений. С помощью лямбда-операции C# (

=>
) можно указывать блок операторов кода (и подлежащие передаче им параметры) везде, где требуется строго типизированный делегат. Как будет показано, лямбда-выражение — не более чем замаскированный анонимный метод и является упрощенным подходом к работе с делегатами. Вдобавок
та же самая операция (в .NEТ Framework 4.6 и последующих версиях) может применяться для реализации метода или свойства, содержащего единственный оператор, посредством лаконичного синтаксиса.

Понятие типа делегата

Прежде чем формально определить делегаты, давайте ненадолго оглянемся назад. Исторически сложилось так, что в API-интерфейсе Windows часто использовались указатели на функции в стиле С для создания сущностей под названием функции обратного вызова или просто обратные вызовы. С помощью обратных вызовов программисты могли конфигурировать одну функцию так, чтобы она обращалась к другой функции в приложении (т.е. делала обратный вызов). С применением такого подхода разработчики Windows-приложений имели возможность обрабатывать щелчки на кнопках, перемещение курсора мыши, выбор пунктов меню и общие двусторонние коммуникации между двумя сущностями в памяти.

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

В частности, делегат поддерживает три важных порции информации:

адрес метода, вызовы которого он делает:

аргументы (если есть) вызываемого метода:

возвращаемое значение (если есть) вызываемого метода.

На заметку! Делегаты .NET Core могут указывать либо на статические методы, либо на методы экземпляра.

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

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

Для определения типа делегата в языке C# применяется ключевое слово

delegate
. Имя типа делегата может быть любым желаемым. Однако сигнатура определяемого делегата должна совпадать с сигнатурой метода или методов, на которые он будет указывать. Например, приведенный ниже тип делегата (по имени
BinaryOp
) может указывать на любой метод, который возвращает целое число и принимает два целых числа в качестве входных параметров (позже в главе вы самостоятельно построите такой делегат, а пока он представлен лишь кратко):

// Этот делегат может указывать на любой метод, который принимает

// два целочисленных значения и возвращает целочисленное значение.

public delegate int BinaryOp(int x, int y);

Когда компилятор C# обрабатывает тип делегата, он автоматически генерирует запечатанный (

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

// -------------------------------------------------------

// TypDefName: SimpleDelegate.BinaryOp

// Extends : System.MulticastDelegate

// Method #1

// -------------------------------------------------------

// MethodName: .ctor

// ReturnType: Void

// 2 Arguments

// Argument #1: Object

// Argument #2: I

// Method #2

// -------------------------------------------------------

// MethodName: Invoke

// ReturnType: I4

// 2 Arguments

// Argument #1: I4

// Argument #2: I4

// 2 Parameters

// (1) ParamToken : Name : x flags: [none]

// (2) ParamToken : Name : y flags: [none] //

// Method #3

// -------------------------------------------------------

// MethodName: BeginInvoke

// ReturnType: Class System.IAsyncResult

// 4 Arguments

// Argument #1: I4

// Argument #2: I4

// Argument #3: Class System.AsyncCallback

// Argument #4: Object

// 4 Parameters

// (1) ParamToken : Name : x flags: [none]

// (2) ParamToken : Name : y flags: [none]

// (3) ParamToken : Name : callback flags: [none]

// (4) ParamToken : Name : object flags: [none]

//

// Method #4

// -------------------------------------------------------

// MethodName: EndInvoke

// ReturnType: I4 (int32)

// 1 Arguments

// Argument #1: Class System.IAsyncResult

// 1 Parameters

// (1) ParamToken : Name : result flags: [none]

Как

видите, в сгенерированном компилятором классе
BinaryOp
определены три открытых метода. Главным методом в .NET Core является
Invoke
, т.к. он используется для вызова каждого метода, поддерживаемого объектом делегата, в синхронной манере; это означает, что вызывающий код должен ожидать завершения вызова, прежде чем продолжить свою работу. Довольно странно, но синхронный метод
Invoke
может не нуждаться в явном вызове внутри вашего кода С#. Вскоре будет показано, что
Invoke
вызывается "за кулисами", когда вы применяете соответствующий синтаксис С#.

На заметку! Несмотря на то что методы

BeginInvoke
и
EndInvoke
генерируются, они не поддерживаются при запуске вашего кода под управлением .NET Core. Это может разочаровывать, поскольку в случае их использования вы получите ошибку не на этапе компиляции, а во время выполнения.

Так благодаря чему же компилятор знает, как определять метод

Invoke
? Для понимания процесса ниже приведен код сгенерированного компилятором класса
BinaryOp
(полужирным курсивом выделены элементы, указанные в определении типа делегата):

Поделиться с друзьями: