Делегаты в C#

Делегаты в 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;

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

  1. SayHello()
  2. About()
  3. 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#. Теперь, получив общее представление о том, что из себя представляют делегаты, мы можем перейти к следующему вопросы — использованию анонимных методов.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии