🖥 Делегаты: разбор основДелегаты представляют объекты, которые указывают на методы. То есть делегаты — это указатели (ссылки) на методы, с помощью которых мы можем вызывать данные методы. По сути, это типобезопасный указатель на функцию, который хранит адрес метода и позволяет вызвать его без необходимости вызывать метод напрямую.
➡️ Определение делегатовДля объявления делегата используется ключевое слово
delegate, после которого идёт возвращаемый тип, название и параметры. Например:
delegate void Foo(int param);
По сути, мы определили новый тип в своей сборке, представляющий собой делегат, который в качестве возвращаемого типа имеет тип
void (т.е. ничего не возвращает) и принимает аргумент типа
int.
Рассмотрим применение этого делегата:
Foo foo = Info; // Создаём переменную делегата и присваиваем этой переменной адрес метода
foo(1); // Вызываем метод. На экран будет выведено "Value is 1"
void Info(int value) => Console.WriteLine($"Value is {value}");
delegate void Foo(int param);
Сам вызов делегата производится подобно вызову метода.
➡️ Место определения делегатаЕсли мы определяем делегат в программах верхнего уровня (top-level program), которую по умолчанию представляет файл
Program.cs, то, как и другие типы, делегат определяется в конце кода. Но делегат можно определять и внутри класса:
public class Program
{
public delegate void Message();
static void Main()
{
// Наш код
// ...
}
}
Либо вне класса:
public delegate void Message();
public class Program
{
static void Main()
{
// Наш код
// ...
}
}
➡️ Добавление методов в делегатВ примерах выше переменная делегата указывала на один метод. В реальности же, делегат может указывать на множество методов, которые имеют ту же сигнатуру и возвращаемый тип. Все методы в делегате попадают в специальный список вызова — invocation list. При вызове делегата все методы из этого списка последовательно вызываются. И мы можем добавлять в этот список не один, а несколько методов:
Message message = Hello;
message += HowAreYou;
message();
void Hello() => Console.WriteLine("Hello");
void HowAreYou() => Console.WriteLine("How are you?");
delegate void Message();
Следует учесть, что мы можем добавить ссылку на один и тот же метод несколько раз, и в списке вызова делегата тогда будет несколько ссылок на один и то же метод. Соответственно, при вызове делегата добавленный метод будет вызываться столько раз, сколько он был добавлен:
Message message = Hello;
message += HowAreYou;
message += Hello;
message += Hello;
message();
Консольный вывод:
Hello
How are you?
Hello
Hello
Подобным образом мы можем удалять методы из делегата:
Message message = Hello;
message += HowAreYou;
message(); // Вызываются методы Hello и HowAreYou
message -= HowAreYou; // Удаляем метод HowAreYou из делегата
message(); // Вызывается метод Hello
Отмечу, что при удалении метода может сложиться ситуация, что в делегате не будет методов, и тогда переменная будет иметь значение
null. Поэтому перед вызовом рекомендуется проверка на
null:
if (message != null) message();
При удалении следует учитывать, что если делегат содержит несколько ссылок на один и тот же метод, то удаление начинается с конца списка вызова делегата и удаляет только первое найденное вхождение.
➡️ Вызов делегатаВ примерах выше, делегат вызывался как обычный метод:
foo(1);
message();
Другой способ вызова делегата представляет метод
Invoke():
Message mes = Hello;
mes.Invoke(); // Вызывается метод Hello
void Hello() => Console.WriteLine("Hello");
delegate void Message();
Если делегат принимает параметры, то в метод
Invoke() передаются значения для этих параметров.
Следует учитывать, что если делегат пуст (равен
null), то при вызове такого делегата мы получим исключение. Поэтому при вызове всегда лучше проверять, не равен ли он
null. Либо можно использовать метод
Invoke() и оператор условного
null:
mes?.Invoke(); // Ошибки не будет, делегат просто не вызывается
#Полезно #Delegate