Содержание
Делегаты в C# — это, видимо, одна из наиболее сложных, но и важных тем для изучения. Мы уже разобрались с тем, что из себя представляет переменная, какие типы данных можно использовать в C#, научились создавать и использовать свои классы и объекты и теперь нам предстоит разобраться с ещё одним типом данных в C# — делегатом.
Объявление делегата в C#
Делегат в C# — это ссылочный тип данных, который предоставляет ссылки на методы. Так, если переменная в C# описывает данные и их тип, то делегат описывает метод, его параметры и тип возвращаемых данных. Упрощенно, можно сказать, что делегат позволяет использовать методы, как обычные переменные.
Для объявления делегата в C# используется ключевое слово delegate
. Например,
delegate void EventHandler();
Здесь мы объявили делегат, который описывает метод без каких либо параметров и который ничего не возвращает. Мы можем объявить любой делегат, например, такой:
delegate double Sum(double x, double y);
После того, как мы объявили делегат, он может указывать на любой метод, имеющий точно такую же сигнатуру, как и у делегата.
Работа с делегатами в C#
Рассмотрим работу с созданными выше делегатами на примере. Для этого создадим новое консольное приложение и напишем следующий код файла Program.cs
//объявляем переменные типа делегата и инициализируем их EventHandler handler = SayHello; Sum SumItUp = Sum; //вызываем делегаты handler(); double summa = SumItUp(10, 5); Console.WriteLine(summa); //создаем необходимые методы void SayHello() { Console.WriteLine("Привет, мир! Это наш первый вызов делегата"); } double Sum(double a, double b) { return a + b; } //объявляем делегаты delegate void EventHandler(); delegate double Sum(double x, double y);
Рассмотрим этот пример подробно. Во-первых, мы создали два делегата:
//объявляем делегаты delegate void EventHandler(); delegate double Sum(double x, double y);
то есть, по сути, мы указали какие сигнатуры должны иметь методы, чтобы мы могли их использовать.
Во-вторых, мы создали два метода:
//создаем необходимые методы void SayHello() { Console.WriteLine("Привет, мир! Это наш первый вызов делегата"); } double Sum(double a, double b) { return a + b; }
Метод SayHello()
соответствует делегату EventHandler
— они ничего не принимает и ничего не возвращает. Метод Sum()
соответствует делегату Sum
— принимает два вещественных числа и возвращает также вещественное число.
В-третьих, мы объявили две переменные типа делегата и инициализировали их:
//объявляем переменные типа делегата и инициализируем их EventHandler handler = SayHello; Sum SumItUp = Sum;
Обратите внимание, что в качестве значения переменной мы указываем имя метода без каких-либо круглых скобок, параметров и т.д.
И, в-четвертых, мы вызвали делегаты, то есть, используя переменную делегата мы вызываем сам метод:
//вызываем делегаты handler(); double summa = SumItUp(10, 5);
В результате, в консоли мы увидим:
15
Возможно, что после воспроизведения этого примера, у вас возникнет вполне резонный вопрос: зачем таки сложности? Ведь мы можем спокойно воспользоваться созданными нами методами — вызвать их обычным способом и не тратить время на создание нового типа данных, инициализацию переменных делегата и так далее. Это вполне резонный вопрос на начальном этапе изучения делегатов C#, на который можно ответить рассмотрев следующий момент.
Делегат как параметр метода
Выше в примере мы объявили две переменный типа делегата и затем, используя их вызвали методы. А раз мы можем объявить переменную, то ничего нам не мешает использовать значение этой переменной в качестве фактического аргумента метода. Допишем наш пример следующим образом:
//вызываем метод с параметрами-делегатами RequestDelegates(SayHello, Sum); //объявляем метод с параметрами- делегатами void RequestDelegates(EventHandler voidDelegate, Sum sumDelegate) { //вызываем меоды voidDelegate(); Console.WriteLine(sumDelegate(10, 5)); } //создаем необходимые методы void SayHello() { Console.WriteLine("Привет, мир! Это наш первый вызов делегата"); } double Sum(double a, double b) { return a + b; } //объявляем делегаты delegate void EventHandler(); delegate double Sum(double x, double y);
Здесь стоит обратить внимание на метод RequestDelegates()
— оба его параметра представляют собой тип делегата. Благодаря этому, мы можем передавать в качестве фактических аргументов этого метода другие методы. В нашем случае, вызов RequestDelegates()
был таким:
//вызываем метод с параметрами-делегатами RequestDelegates(SayHello, Sum);
Результатом выполнения программы будет такой же вывод в консоли, как и представленный выше. Это одно из преимуществ использования делегатов, которое очень активно применяется при разработке приложений в C#. Например, такое применение делегатов нашло широкое применение при разработке приложений ASP.NET Core.
Добавление методов в делегат
С переменными типа делегата мы можем выполнять операции сложения и вычитания. Добавление методов в делегат — это ещё одна важная особенность этого типа данных в C#. При добавлении метода в делегат он попадает в специальный список InvocationList
и при вызове делегата каждый метод из этого списка последовательно выполняется. Например, изменим наше приложение следующим образом:
//вызываем метод с параметрами-делегатами EventHandler writeText = SayHello; writeText += About; RequestDelegates(writeText, Sum); //объявляем метод с параметрами- делегатами void RequestDelegates(EventHandler voidDelegate, Sum sumDelegate) { //вызываем меоды voidDelegate(); Console.WriteLine(sumDelegate(1, 1)); } //создаем необходимые методы void About() { Console.WriteLine("Эта программа демонстрирует работу с делегатами"); } void SayHello() { Console.WriteLine("Привет, мир! Это наш первый вызов делегата"); } double Sum(double a, double b) { return a + b; } //объявляем делегаты delegate void EventHandler(); delegate double Sum(double x, double y);
Обратите внимание на первые две строки:
EventHandler writeText = SayHello; writeText += About;
Здесь мы прибавляем к делегату ещё один метод, используя оператор составного присваивания. Теперь, после запуска программы вызов методов будет осуществяться следующим образом:
SayHello()
About()
Sum()
Аналогичным образом мы можем не прибавлять, а отнимать методы:
void RequestDelegates(EventHandler voidDelegate, Sum sumDelegate) { //вызываем меоды voidDelegate(); voidDelegate -= SayHello; //вычитаем метод из делегата voidDelegate(); Console.WriteLine(sumDelegate(1, 1)); }
Теперь вывод консоли будет следующим:
Эта программа демонстрирует работу с делегатами
Эта программа демонстрирует работу с делегатами
2
Более того, мы можем применять операции сложения непосредственно к переменным делегатов, например:
EventHandler writeText = SayHello; EventHandler writeAbout = About; EventHandler writeAll = writeText + writeAbout;
Здесь стоит еще раз обратить внимание на то, что методы, на которые ссылается переменная делегата вызывается в том порядке в котором они были добавлены.
При сложении и вычитании делегатов следует следить за тем, чтобы в InvocationList
содержался хотя бы один метод. Например, следующим код приведет к вызову исключения:
EventHandler writeText = SayHello; writeText -= SayHello; //InvocationList пустой writeText();
Чтобы проверить возможность вызова делегата мы можем провести проверку переменной типа делегата на null
:
EventHandler writeText = SayHello; writeText -= SayHello; //InvocationList пустой if (writeText == null) Console.WriteLine("Невозможно выполнить метод делегата");
или же воспользоваться оператором условного null
и методом Invoke()
делегата:
writeText?.Invoke();
И здесь мы можем перейти к следующему моменту работы с делегатами — вариантам вызова делегатов.
Варианты вызова делегатов
Так как типы делегата являются производными от System.Delegate
, то в делегате можно вызывать методы и свойства, определенные этим классом. До сих пор мы вызывали делегат как обычный метод. Используя же возможностиSystem.Delegate
мы можем воспользоваться для вызова специальным методом Invoke()
.
Ниже представлены два равнозначных способа вызова делегата:
EventHandler HelloHandler = new EventHandler(SayHello); HelloHandler(); HelloHandler.Invoke();
Если делегат имеет параметры, то мы можем их передавать в параметрах метода Invoke()
, например:
delegate double Sum(double x, double y); Sum sumDelegate = Sum; sumDelegate.Invoke(50, 50);
Создание делегатов
Так как делегаты относятся к ссылочным типам данных, то переменные такого типа мы можем создавать также, как и обычные объекты, передавая метод делегата непосредственно в конструкторе:
Sum sumDelegate = new Sum(Sum); sumDelegate.Invoke(50, 50);
Итого
Сегодня мы в общих чертах изучили, что из себя представляют делегаты в C#, научились создавать переменные типа делегата и вызывать методы с использованием делегатов в C#. Теперь, получив общее представление о том, что из себя представляют делегаты, мы можем перейти к следующему вопросы — использованию анонимных методов.