BinaryOp b = new BinaryOp(SimpleMath.Add);
<b>// Вызвать метод Add() косвенно с использованием объекта делегата.</b>
Console.WriteLine("10 + 10 is {0}", b(10, 10));
Console.ReadLine();
// Дополнительные определения типов должны находиться
// в конце операторов верхнего уровня.
// Этот делегат может указывать на любой метод,
// принимающий два целых числа и возвращающий целое число.
public delegate int BinaryOp(int x, int y);
На заметку! Вспомните из главы 3, что дополнительные определения типов (делегат
BinaryOp
в этом примере) должны располагаться после всех операторов верхнего уровня.
И снова обратите внимание на формат объявления типа делегата
BinaryOp
; он определяет, что объекты делегата
BinaryOp
могут указывать на любой метод, принимающий два целочисленных значения и возвращающий целочисленное значение (действительное имя метода, на который он указывает, к делу не относится). Здесь мы создали класс по имени
SimpleMath
, определяющий два статических метода, которые соответствуют шаблону, определяемому делегатом B
inaryOp
.
Когда вы хотите присвоить целевой метод заданному объекту делегата, просто передайте имя нужного метода конструктору делегата:
// Создать объект делегата BinaryOp, который
// "указывает" на SimpleMath.Add().
BinaryOp b = new BinaryOp(SimpleMath.Add);
На данной стадии метод, на который указывает делегат, можно вызывать с использованием синтаксиса, выглядящего подобным прямому вызову функции:
// На самом деле здесь вызывается метод Invoke()!
Console.WriteLine("10 + 10 is {0}", b(10, 10));
"За кулисами" исполняющая среда вызывает сгенерированный компилятором метод
Invoke()
на вашем производном от
MulticastDelegate
классе. В этом можно удостовериться, открыв сборку в утилите
ildasm.exe
и просмотрев код CIL внутри метода
Main()
:
.method private hidebysig static void Main(string[] args) cil managed
{
...
callvirt instance int32 BinaryOp::Invoke(int32, int32)
}
Язык C# вовсе не требует явного вызова метода
Invoke()
внутри вашего кода. Поскольку
BinaryOp
может указывать на методы, которые принимают два аргумента, следующий оператор тоже допустим:
Console.WriteLine("10 + 10 is {0}", b.Invoke(10, 10));
Вспомните, что делегаты .NET Core безопасны в отношении типов. Следовательно, если вы попытаетесь передать делегату метод, который не соответствует его шаблону, то получите ошибку на этапе компиляции. В целях иллюстрации предположим, что в классе
SimpleMath
теперь определен дополнительный метод по имени
SquareNumber()
, принимающий единственный целочисленный аргумент:
public class SimpleMath
{
public static int SquareNumber(int a) => a * a;
}
Учитывая, что делегат
BinaryOp
может указывать только на методы, которые принимают два целочисленных значения и возвращают целочисленное значение, представленный ниже код некорректен и приведет к ошибке на этапе компиляции:
// Ошибка на этапе компиляции! Метод не соответствует шаблону делегата!
BinaryOp b2 = new BinaryOp(SimpleMath.SquareNumber);
Исследование объекта делегата
Давайте усложним текущий пример, создав в классе
Program
статический метод (по имени
DisplayDelegatelnfо()
). Он будет выводить на консоль имена методов, поддерживаемых объектом делегата, а также имя класса, определяющего метод. Для этого организуется итерация по массиву
System.Delegate
, возвращенному методом
GetlnvocationList()
, с обращением к свойствам
Target
и
Method
каждого объекта:
static void DisplayDelegateInfo(Delegate delObj)
{
// Вывести имена всех членов в списке вызовов делегата.
foreach (Delegate d in delObj.GetInvocationList())
{
Console.WriteLine("Method Name: {0}", d.Method); // имя метода
Console.WriteLine("Type Name: {0}", d.Target); // имя типа
}
}
Предполагая, что в метод
Main()
добавлен вызов нового вспомогательного метода:
BinaryOp b = new BinaryOp(SimpleMath.Add);
DisplayDelegateInfo(b);
вывод приложения будет таким:
***** Simple Delegate Example *****
Method Name: Int32 Add(Int32, Int32)
Type Name:
10 + 10 is 20
Обратите внимание, что при обращении к свойству
Target
имя целевого класса (
SimpleMath
) в настоящий момент не отображается. Причина в том, что делегат
BinaryOp
указывает на
статический метод, и потому объект для ссылки попросту отсутствует! Однако если сделать методы
Add()
и
Substract()
нестатическими (удалив ключевое слово
static
из их объявлений), тогда можно будет создавать экземпляр класса
SimpleMat
h и указывать методы для вызова с применением ссылки на объект: