Полное руководство по языку программирования С# 14 и платформе .NET 10 / Делегаты, события и лямбды

Применение делегатов

Начальный ⏱ 7 мин чтения 👁 2 🕓 обновлено 11.08.2026

В прошлой теме подробно были рассмотрены делегаты. Однако данные примеры, возможно, не показывают истинной силы делегатов, так как нужные нам методы в данном случае мы можем вызвать и напрямую без всяких делегатов. Однако наиболее сильная сторона делегатов состоит в том, что они позволяют делегировать выполнение некоторому коду извне. И на момент написания программы мы можем не знать, что за код будет выполняться. Мы просто вызываем делегат. А какой метод будет непосредственно выполняться при вызове делегата, будет решаться потом.

Рассмотрим подробный пример. Пусть у нас есть класс, описывающий счет в банке:


public class Account
{
    int sum; // Переменная для хранения суммы
    // через конструктор устанавливается начальная сумма на счете
    public Account(int sum) => this.sum = sum;
    // добавить средства на счет
    public void Add(int sum) => this.sum += sum;
    // взять деньги с счета
    public void Take(int sum)
    {
        // берем деньги, если на счете достаточно средств
        if (this.sum >=sum) this.sum -= sum;
    }
}

В переменной sum хранится сумма на счете. С помощью конструктора устанавливается начальная сумма на счете. Метод Add() служит для добавления на счет, а метод Take - для снятия денег со счета.

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


public class Account
{
    int sum;
    public Account(int sum) => this.sum = sum;
    public void Add(int sum) => this.sum += sum;
    public void Take(int sum)
    {
        if (this.sum >= sum)
        {
            this.sum -= sum;
            Console.WriteLine($"Со счета списано {sum} у.е.");
        }
    }
}

Но что если наш класс планируется использовать в других проектах, например, в графическом приложении на Windows Forms или WPF, в мобильном приложении, в веб-приложении. Там строка уведомления

Console.WriteLine($"Со счета списано {sum} у.е.");

не будет иметь большого смысла.

Более того, наш класс Account будет использоваться другими разработчиками в виде отдельной библиотеки классов. И эти разработчики захотят уведомлять о снятии средств каким-то другим образом, о которых мы даже можем не догадываться на момент написания класса. Поэтому примитивое уведомление в виде строки кода

Console.WriteLine($"Со счета списано {sum} у.е.");

не самое лучшее решение в данном случае. И делегаты позволяют делегировать определение действия из класса во внешний код, который будет использовать этот класс.

Изменим класс, применив делегаты:


// Объявляем делегат
public delegate void AccountHandler(string message);
public class Account
{
    int sum;
    // Создаем переменную делегата
    AccountHandler? taken;
    public Account(int sum) => this.sum = sum;
    // Регистрируем делегат
    public void RegisterHandler(AccountHandler del)
    {
        taken = del;
    }
    public void Add(int sum) => this.sum += sum;
    public void Take(int sum)
    {
        if (this.sum >= sum)
        {
            this.sum -= sum;
            // вызываем делегат, передавая ему сообщение
            taken?.Invoke($"Со счета списано {sum} у.е.");
        }
        else
        {
            taken?.Invoke($"Недостаточно средств. Баланс: {this.sum} у.е.");
        }
    }
}

Для делегирования действия здесь определен делегат AccountHandler. Этот делегат соответствует любым методам, которые имеют тип void и принимают параметр типа string.

public delegate void AccountHandler(string message);

В классе Account определяем переменную taken, которая представляет этот делегат:

AccountHandler? taken;

Теперь надо связать эту переменную с конкретным действием, которое будет выполняться. Мы можем использовать разные способы для передачи делегата в класс. В данном случае определяется специальный метод RegisterHandler, в котором в переменную taken передается реальное действие:


public void RegisterHandler(AccountHandler del)
{
	taken = del;
}

Таким образом, делегат установлен, и теперь его можно вызывать. Вызов делегата производится в методе Take:


public void Take(int sum)
{
    if (this.sum >= sum)
    {
        this.sum -= sum;
        // вызываем делегат, передавая ему сообщение
        taken?.Invoke($"Со счета списано {sum} у.е.");
    }
	else
	{
		taken?.Invoke($"Недостаточно средств. Баланс: {this.sum} у.е.");
	}
}

Поскольку делегат AccountHandler в качестве параметра принимает строку, то при вызове переменной taken() мы можем передать в этот вызов конкретное сообщение. В зависимости от того, произошло снятие денег или нет, в вызов делегата передаются разные сообщения.

То есть фактически вместо делегата будут выполняться действия, которые переданы делегату в методе RegisterHandler. Причем опять же подчеркну, при вызове делегата мы не знаем, что это будут за действия. Здесь мы только передаем в эти действия сообщение об успешно или неудачном снятии.

Теперь протестируем класс в основной программе:


// создаем банковский счет
Account account = new Account(200);
// Добавляем в делегат ссылку на метод PrintSimpleMessage
account.RegisterHandler(PrintSimpleMessage);
// Два раза подряд пытаемся снять деньги
account.Take(100);
account.Take(150);

void PrintSimpleMessage(string message) => Console.WriteLine(message);

Здесь через метод RegisterHandler переменной taken в классе Account передается ссылка на метод PrintSimpleMessage. Этот метод соответствует делегату AccountHandler. Соответственно там, где в классе Account вызывается делегат taken, в реальности будет выполняться метод PrintSimpleMessage.

Через параметр message метод PrintSimpleMessage получит переданное из делегата сообщение и выведет его на консоль:


Со счета списано 100 у.е.
Недостаточно средств. Баланс: 100 у.е.

Таким образом, мы создали механизм обратного вызова для класса Account, который срабатывает в случае снятия денег. Здесь мы выводим сообщение на консоль. Да, мы могли бы просто выводить сообщение на консоль и без делегатов. Однако с делегатом для класса Account не важно, как это сообщение выводится. Классу Account даже не известно, что вообще будет делаться в результате списания денег. Он просто посылает уведомление об этом через делегат.

В результате, если мы создаем консольное приложение, мы можем через делегат выводить сообщение на консоль. Если мы создаем графическое приложение Windows Forms или WPF, то можно выводить сообщение в виде графического окна. А можно не просто выводить сообщение. А, например, записать при списании информацию об этом действии в файл или отправить уведомление на электронную почту. В общем любыми способами обработать вызов делегата. И способ обработки не будет зависеть от класса Account.

Добавление и удаление методов в делегате

Хотя в примере наш делегат принимал адрес на один метод, в действительности он может указывать сразу на несколько методов. Кроме того, при необходимости мы можем удалить ссылки на адреса определенных методов, чтобы они не вызывались при вызове делегата. Итак, изменим в классе Account метод RegisterHandler и добавим новый метод UnregisterHandler, который будет удалять методы из списка методов делегата:


public delegate void AccountHandler(string message);
public class Account
{
    int sum;
    AccountHandler? taken;
    public Account(int sum) => this.sum = sum;
    // Регистрируем делегат
    public void RegisterHandler(AccountHandler del)
    {
        taken += del;
    }
    // Отмена регистрации делегата
    public void UnregisterHandler(AccountHandler del)
    {
        taken -= del; // удаляем делегат
    }
    public void Add(int sum) => this.sum += sum;
    public void Take(int sum)
    {
        if (this.sum >= sum)
        {
            this.sum -= sum;
            taken?.Invoke($"Со счета списано {sum} у.е.");
        }
        else 
            taken?.Invoke($"Недостаточно средств. Баланс: {this.sum} у.е.");
    }
}

В первом методе объединяет делегаты taken и del в один, который потом присваивается переменной taken. Во втором методе из переменной taken удаляется делегат del.

Применим новые методы:


Account account = new Account(200);
// Добавляем в делегат ссылку на методы
account.RegisterHandler(PrintSimpleMessage);
account.RegisterHandler(PrintColorMessage);
// Два раза подряд пытаемся снять деньги
account.Take(100);
account.Take(150);

// Удаляем делегат
account.UnregisterHandler(PrintColorMessage);
// снова пытаемся снять деньги
account.Take(50);

void PrintSimpleMessage(string message) => Console.WriteLine(message);
void PrintColorMessage(string message)
{
    // Устанавливаем красный цвет символов
    Console.ForegroundColor = ConsoleColor.Red;
    Console.WriteLine(message);
    // Сбрасываем настройки цвета
    Console.ResetColor();
}

В целях тестирования мы создали еще один метод - PrintColorMessage, который выводит то же самое сообщение только красным цветом. Ссылка на этот метод также передается в метод RegisterHandler, и таким образом ее получит переменная taken.

В строке account.UnregisterHandler(PrintColorMessage); этот метод удаляется из списка вызовов делегата, поэтому этот метод больше не будет срабатывать. Консольный вывод будет иметь следующую форму:

Со счета списано 100 у.е.
Со счета списано 100 у.е.
Недостаточно средств. Баланс: 100 у.е.
Недостаточно средств. Баланс: 100 у.е.
Со счета списано 50 у.е.

Проверь себя

📋 Тест по теме «Применение делегатов» Тест по теме

Практические задания

Лёгкое Ручная проверка Заполните пропуск в коде (1)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

public class _____
{
    int sum; // Переменная для хранения суммы
    // через конструктор устанавливается начальная сумма на счете
    public Account(int sum) => this.sum = sum;
    // добавить средства на счет
    public void Add(int sum) => this.sum += sum;
    // взять деньги с счета
    public void Take(int sum)
    {
        // берем деньги, если на счете достаточно средств
        if (this.sum >=sum) this.sum -= sum;
    }
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (2)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

public class Account
{
    int _____;
    public Account(int sum) => this.sum = sum;
    public void Add(int sum) => this.sum += sum;
    public void Take(int sum)
    {
        if (this.sum >= sum)
        {
            this.sum -= sum;
            Console.WriteLine($"Со счета списано {sum} у.е.");
        }
    }
}
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (3)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

Console.WriteLine($"Со счета списано {_____} у.е.");
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (4)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

_____.WriteLine($"Со счета списано {sum} у.е.");
Войдите, чтобы отправить решение
Лёгкое Ручная проверка Заполните пропуск в коде (5)
+10 XP

В приведённом ниже фрагменте кода из этого урока пропущено одно слово. Определите его и впишите в поле ответа (без кавычек и пробелов).

// Объявляем делегат
public delegate void AccountHandler(string message);
public class Account
{
    int _____;
    // Создаем переменную делегата
    AccountHandler? taken;
    public Account(int sum) => this.sum = sum;
    // Регистрируем делегат
    public void RegisterHandler(AccountHandler del)
    {
        taken = del;
    }
    public void Add(int sum) => this.sum += sum;
    public void Take(int sum)
    {
        if (this.sum >= sum)
        {
            this.sum -= sum;
            // вызываем делегат, передавая ему сообщение
            taken?.Invoke($"Со счета списано {sum} у.е.");
        }
        else
        {
            taken?.Invoke($"Недостаточно средств. Баланс: {this.sum} у.е.");
        }
    }
}
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (1)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

public class Account
{
    int _____; // Переменная для хранения суммы
    // через конструктор устанавливается начальная сумма на счете
    public Account(int sum) => _____.sum = sum;
    // добавить средства на счет
    public void Add(int sum) => this.sum += sum;
    // взять деньги с счета
    public void Take(int sum)
    {
        // берем деньги, если на счете достаточно средств
        if (this.sum >=sum) this.sum -= sum;
    }
}
Войдите, чтобы отправить решение
Среднее Ручная проверка Восстановите код по памяти (2)
+20 XP

В этом фрагменте кода урока пропущены два ключевых элемента. Впишите оба пропущенных значения через запятую в порядке появления (например: x, 5).

public class Account
{
    int _____;
    public Account(int _____) => this.sum = sum;
    public void Add(int sum) => this.sum += sum;
    public void Take(int sum)
    {
        if (this.sum >= sum)
        {
            this.sum -= sum;
            Console.WriteLine($"Со счета списано {sum} у.е.");
        }
    }
}
Войдите, чтобы отправить решение
Среднее Ручная проверка Расставьте строки кода в правильном порядке
+20 XP

Строки кода из примера этого урока перемешаны. Впишите их в правильном порядке, каждую строку — с новой строки.

{
}
public void RegisterHandler(AccountHandler del)
	taken = del;
Войдите, чтобы отправить решение
Сложное Ручная проверка Объясните и модифицируйте пример из урока «Применение делегатов»
+30 XP

Опишите своими словами, что делает приведённый пример кода из урока «Применение делегатов», и предложите одно осмысленное изменение (например, другое входное значение или дополнительное условие). Приведите итоговый код.

public class Account
{
    int sum; // Переменная для хранения суммы
    // через конструктор устанавливается начальная сумма на счете
    public Account(int sum) => this.sum = sum;
    // добавить средства на счет
    public void Add(int sum) => this.sum += sum;
    // взять деньги с счета
    public void Take(int sum)
    {
        // берем деньги, если на счете достаточно средств
        if (this.sum >=sum) this.sum -= sum;
    }
}
Войдите, чтобы отправить решение
Сложное Ручная проверка Найдите потенциальную проблему в коде урока «Применение делегатов»
+30 XP

Изучите приведённый фрагмент кода и опишите, при каких входных данных или условиях он может сработать не так, как ожидается (граничный случай, отсутствие проверки и т.п.). Предложите исправленный вариант.

public class Account
{
    int sum;
    public Account(int sum) => this.sum = sum;
    public void Add(int sum) => this.sum += sum;
    public void Take(int sum)
    {
        if (this.sum >= sum)
        {
            this.sum -= sum;
            Console.WriteLine($"Со счета списано {sum} у.е.");
        }
    }
}
Войдите, чтобы отправить решение
Мини-проект Ручная проверка Мини-проект по теме «Применение делегатов»
+50 XP

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

Войдите, чтобы отправить решение
Домашнее задание Ручная проверка Домашнее задание по теме «Применение делегатов»
+25 XP

Напишите собственную небольшую программу, применяющую материал темы «Применение делегатов». Используйте не менее одной конструкции, показанной в примерах урока. Прикрепите код решения и краткое описание того, что он делает.

Войдите, чтобы отправить решение

Обсуждение (0)

Войдите, чтобы оставить комментарий.