Урок 14. Делегаты C#


Четырнадцатый урок учебника по объектно-ориентированному программированию на языке C# объясняет, как добавлять делегаты в классы и программы. Делегаты используются для предоставления ссылок на методы, которые могут быть изменены во время выполнения. Они также необходимы при создании событий.

Что такое делегат?

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

Примечание: если вы ранее использовали указатели функций C++, то вы увидите много общего между ними и делегатами. В отличие от указателей функций, делегаты C# являются объектно-ориентированными, безопасными и типобезопасными.

Многоадресная передача

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

Асинхронный обратный вызов

Еще одно интересное использование делегатов известно как асинхронный обратный вызов. Поскольку делегат является объектом, он может передаваться как параметр или присваиваться как значение свойства. Это позволяет создать делегат и передать его в длительный асинхронный процесс. Когда процесс завершает выполнение, делегат может быть вызван для указания завершения. Асинхронные обратные вызовы выходят за рамки данной статьи.

Создание делегата

Для создания делегата ключевое слово delegate используется в качестве префикса подписи метода следующим образом:

delegate return-type name(parameters);
Возвращаемый тип - это тип данных, который будет возвращен из метода, на который ссылается делегат. Имя-это имя делегата, а раздел Параметры содержит разделенный запятыми список параметров, используемых при вызове делегата. Возвращаемый тип и параметры формируют сигнатуру делегата, и она должна соответствовать сигнатуре метода, связанного с ним, хотя ссылочный метод может быть статическим или связанным с конкретным экземпляром класса.

Создание простого делегата

Чтобы продемонстрировать объявление и использование простого делегата, мы создадим программу в качестве примера. Для начала создайте новое консольное приложение с именем "DelegateDemo". Добавьте в проект файл класса с именем "Scoreboard". Этот класс будет содержать методы, вычисляющие счет для фиктивной игры, которая требует, чтобы игрок поразил цели в кратчайшие сроки. Версии расчета будут доступны для взрослых и детей, и мы будем использовать делегат, чтобы определить, какой из них используется.

Добавьте следующий код в класс Scoreboard, чтобы создать два механизма подсчета очков:

// 10 баллов за цель, 2 балла в секунду
public static int CalculateAdultScore(int seconds, int targets)
{
    return (targets * 10) - (seconds * 2);
}
// 15 очков на цель, -1 очко в секунду
public static int CalculateChildScore(int seconds, int targets)
{
    return (targets * 15) - seconds;
}
Объявление делегата

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

delegate int CalculateScore(int seconds, int targets);
Примечание: в примере параметры имеют те же имена, что и имена методов, на которые будет ссылаться делегат. Это не является обязательным требованием, хотя типы данных должны совпадать.

Ссылка на метод

Наконец, нам нужно связать целевой метод с делегатом, чтобы его можно было вызвать. Добавьте следующий код к основному методу:

CalculateScore getScore;
int time = 60;
int targets = 20;
 
getScore = new CalculateScore(Scoreboard.CalculateAdultScore);
Console.WriteLine("Adult score: {0}", getScore(time, targets));
 
getScore = new CalculateScore(Scoreboard.CalculateChildScore);
Console.WriteLine("Child score: {0}", getScore(time, targets));
 
/* OUTPUT
 
Adult score: 80
Child score: 240
 
*/
Код в основном методе начинается с объявления объекта с именем "getScore" на основе делегата CalculateScore. Этот объект будет вызван вместо целевого метода, назначенного во время выполнения. Эти два целых числа просто представляют данные за затраченное время и количество пораженных целей, которые будут использоваться для расчета баллов.

Затем инициализируется объект getScore. Это похоже по синтаксису на создание экземпляра любого другого объекта. Ключевое отличие состоит в том, что параметр, который требуется, является ссылкой на метод, который должен быть связан с объектом делегата. В этом случае, поскольку вызываемый метод является статическим, имена классов и методов предоставляются. Если бы это был метод экземпляра, вы бы заменили имя класса именем объекта.

Первая Console.WriteLine выводит вычисленный балл с использованием правил подсчета очков для взрослых. Он делает это путем вызова объекта делегата getScore, как если бы это был ссылочный метод. Делегат эффективно передает управление методу CalculateAdultScore.

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

Удаление ссылки

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

getScore = null;
Создание делегата многоадресной рассылки

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

Чтобы продемонстрировать объявление и использование делегата многоадресной рассылки, мы изменим класс Scoreboard и основной метод ранее созданной программы. Мы начнем с добавления двух методов в класс Scoreboard, каждый из которых предоставляет возможность вывода сообщения о состоянии на консоль.

Добавьте следующий код в класс табло, чтобы создать два метода сообщения о состоянии:

// Сообщение о попадании в цель
public void TargetHit()
{
    Console.WriteLine("Target hit!");
} 

// Сообщение о начислении баллов 
public void PointsAwarded()
{
    Console.WriteLine("Points awarded!");
}
Примечание: обратите внимание, что на этот раз мы используем экземпляр, а не статические методы, поэтому будем вызывать методы экземпляра объекта Scoreboard.

Объявление делегата

Объявление делегата многоадресной рассылки ничем не отличается от объявления простого делегата. Чтобы создать новый делегат, добавьте следующий код в класс приложения "Program".

delegate void ShowStatus();
Ссылки на несколько методов

Добавление первого метода к делегату использует тот же синтаксис, что и в предыдущем примере. Для добавления второй и последующих ссылок на метод используется оператор сложного присваивания сложения ( + = ). Следующий код показывает это, выполняя оба метода отображения состояния из одного вызова делегата. Чтобы проверить образец, замените содержимое основного метода приведенным ниже кодом:

Scoreboard board = new Scoreboard();
 
ShowStatus points = new ShowStatus(board.PointsAwarded);
ShowStatus hit = new ShowStatus(board.TargetHit);
 
ShowStatus display = points;
display += hit;
 
display();
 
/* 
 
Points awarded!
Target hit!
 
В этом коде необходим экземпляр Scoreboard, поэтому он объявляется первым, а затем следует индивидуальное объявление для двух делегатов, ссылающихся на два метода. Затем создается делегат "display", который будет являться делегатом многоадресной рассылки, и ему присваивается значение "points". С помощью сложного оператора присваивания второй делегат добавляется в цепочку вызова, так что при вызове display выполняются оба базовых метода.

Удаление ссылок

Как и в случае с простыми делегатами, важно освободить ссылки, когда они больше не требуются. Чтобы освободить все ссылки сразу, объекты делегата можно просто установить в null, как описано ранее. Однако когда-нибудь вы захотите отказаться от подписки на отдельные ссылки. Это достигается с помощью сложного оператора вычитания. Например, чтобы отменить подписку и освободить ссылку "точки", вы должны использовать следующий код:

display -= points;
points = null;
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегистатьи IT, си шарп, ООП




Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.




Windows Portable Devices: получаем список папок и файлов на устройстве
Рекурсия Java в примерах
Методы в C#