Делегаты и события
Делегаты и события в C# являются ключевыми механизмами для построения гибкой и модульной архитектуры приложений. Делегат — это типобезопасный указатель на метод, который позволяет передавать методы как параметры, хранить их и вызывать динамически во время выполнения. События строятся на основе делегатов и реализуют паттерн "издатель-подписчик", позволяя объектам уведомлять другие объекты о произошедших действиях, при этом минимизируя их взаимозависимость.
Использование делегатов и событий оправдано в системах уведомлений, обработке пользовательского интерфейса, логировании, плагин-системах и асинхронных процессах. Делегаты обеспечивают вызов методов без жесткой привязки к конкретной реализации, а события позволяют нескольким подписчикам реагировать на одно действие издателя.
В этом руководстве читатель научится создавать, подписывать и вызывать делегаты, определять события и подписываться на них, а также интегрировать эти механизмы с принципами ООП, структурами данных и алгоритмами для решения практических задач в C#.
Базовый Пример
textusing System;
namespace DelegatesAndEventsDemo
{
public delegate void NotificationHandler(string message);
public class Publisher
{
public event NotificationHandler Notify;
public void SendNotification(string message)
{
Notify?.Invoke(message);
}
}
public class Subscriber
{
private string _name;
public Subscriber(string name)
{
_name = name;
}
public void OnNotificationReceived(string message)
{
Console.WriteLine($"{_name} получил сообщение: {message}");
}
}
class Program
{
static void Main(string[] args)
{
Publisher publisher = new Publisher();
Subscriber alice = new Subscriber("Алиса");
Subscriber bob = new Subscriber("Боб");
publisher.Notify += alice.OnNotificationReceived;
publisher.Notify += bob.OnNotificationReceived;
publisher.SendNotification("Привет всем подписчикам!");
publisher.Notify -= bob.OnNotificationReceived;
publisher.SendNotification("Второе уведомление");
Console.ReadLine();
}
}
В этом примере делегат NotificationHandler
определяет сигнатуру методов для уведомлений, обеспечивая типобезопасность. Класс Publisher
содержит событие Notify
, которое вызывается через оператор ?.Invoke()
, чтобы гарантировать безопасный вызов только при наличии подписчиков.
Класс Subscriber
реализует метод OnNotificationReceived
, соответствующий сигнатуре делегата. В основном методе создаются объекты Publisher и Subscriber, подписка и отписка на события демонстрируют управление жизненным циклом подписчиков. Такой подход реализует слабую связанность компонентов, безопасный вызов событий и предотвращает утечки памяти.
Практический Пример
textusing System;
using System.Collections.Generic;
namespace DelegatesAndEventsAdvanced
{
public delegate void DataProcessedHandler(int result);
public class DataProcessor
{
public event DataProcessedHandler DataProcessed;
public void ProcessData(List<int> data)
{
int sum = 0;
foreach (var num in data)
{
if (num < 0)
{
Console.WriteLine("Пропущено отрицательное значение: " + num);
continue;
}
sum += num;
}
DataProcessed?.Invoke(sum);
}
}
public class Logger
{
public void LogResult(int result)
{
Console.WriteLine($"Результат записан: {result}");
}
}
public class Notifier
{
public void SendAlert(int result)
{
if (result > 50)
Console.WriteLine("Внимание! Результат превышает порог: " + result);
}
}
class Program
{
static void Main(string[] args)
{
DataProcessor processor = new DataProcessor();
Logger logger = new Logger();
Notifier notifier = new Notifier();
processor.DataProcessed += logger.LogResult;
processor.DataProcessed += notifier.SendAlert;
List<int> sampleData = new List<int> { 10, 20, 30, -5 };
processor.ProcessData(sampleData);
Console.ReadLine();
}
}
В этом примере DataProcessor
суммирует положительные элементы списка и игнорирует отрицательные значения. Событие DataProcessed
позволяет классам Logger и Notifier реагировать на результат, не зная деталей вычислений. Logger записывает результат, а Notifier отправляет предупреждение при превышении порога.
Пример демонстрирует паттерн Observer, соблюдение принципа единой ответственности (SRP), безопасный вызов событий, слабую связанность компонентов и обработку исключений.
C# Best Practices и типичные ошибки:
- Использовать типобезопасные делегаты и инкапсулированные события.
- Вызывать событие через
?.Invoke()
для предотвращения NullReferenceException. - Удалять подписчиков, когда они больше не нужны, чтобы избежать утечек памяти.
- Внимательно использовать анонимные делегаты для подписки.
- Не выполнять долгие операции на основном потоке при частых событиях.
- Минимизировать выделение объектов в высокочастотных событиях.
- Проверять корректность данных перед вызовом событий.
📊 Справочная Таблица
C# Element/Concept | Description | Usage Example |
---|---|---|
Delegate | Типобезопасный указатель на метод | public delegate void MyDelegate(int x); |
Event | Событие на основе делегата | public event MyDelegate MyEvent; |
Subscription | Подписка на событие | myPublisher.MyEvent += mySubscriber.MyMethod; |
Unsubscription | Отписка от события | myPublisher.MyEvent -= mySubscriber.MyMethod; |
Conditional Invocation | Безопасный вызов события | MyEvent?.Invoke(42); |
Anonymous Delegate | Определение делегата inline | myPublisher.MyEvent += (x) => Console.WriteLine(x); |
Резюме и следующие шаги:
Делегаты и события обеспечивают гибкую, слабо связанную архитектуру C# приложений. Основные концепции: типобезопасные делегаты, определение событий, безопасная подписка/отписка и применение паттерна Observer.
Для дальнейшего изучения рекомендуется освоить асинхронное программирование, интеграцию делегатов с LINQ и архитектуру на основе событий. Практические применения включают системы уведомлений, фреймворки плагинов и обработку данных в реальном времени. Microsoft Docs и open-source проекты предоставляют дополнительные ресурсы.
🧠 Проверьте Свои Знания
Test Your Knowledge
Test your understanding of this topic with practical questions.
📝 Инструкции
- Внимательно прочитайте каждый вопрос
- Выберите лучший ответ на каждый вопрос
- Вы можете пересдавать тест столько раз, сколько захотите
- Ваш прогресс будет показан вверху