Event Bus и паттерн Observer в C# (.NET): реализация для микросервисов и GUI

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Event Bus и паттерн Observer в C# (.NET): от теории к реализации

В современной разработке на C# (.NET) часто возникает необходимость организовать взаимодействие между компонентами системы, сохраняя их независимость. Паттерн Observer и его эволюция — Event Bus (Шина событий) — являются ключевыми инструментами для реализации слабого связывания (loose coupling). В этой статье мы разберём, как работают эти механизмы, и создадим простую, но функциональную реализацию Event Bus на C#.

Паттерн Observer: классический подход

Паттерн Observer определяет отношение "один ко многим" между объектами: когда один объект (издатель) меняет своё состояние, все зависимые объекты (подписчики) автоматически уведомляются. В .NET этот паттерн встроен через события (event) и делегаты.

Недостатки классической реализации:

  • Прямая зависимость подписчика от издателя (нужно хранить ссылку на объект).
  • Сложность управления подписками в больших системах.
  • Проблемы с потоками (многопоточность требует дополнительной синхронизации).

Event Bus: централизованный посредник

Event Bus решает эти проблемы, выступая в роли центрального посредника. Вместо того чтобы подписываться напрямую на конкретный объект, компоненты регистрируются в шине событий. Издатели публикуют события в шину, а шина рассылает их всем подписчикам. Это реализует паттерн Pub/Sub (Publish/Subscribe).

Преимущества Event Bus:

  • Слабая связанность: издатели и подписчики ничего не знают друг о друге.
  • Масштабируемость: легко добавлять новых подписчиков.
  • Универсальность: подходит для GUI (WPF/WinForms), микросервисов и игр (Unity).

Реализация Event Bus на C# (.NET)

Мы создадим базовую реализацию Event Bus, которая поддерживает асинхронную обработку и типизированные события. Для этого используем интерфейс IEvent и обобщённый класс EventBus.

Шаг 1: Определяем базовые типы.

// Базовый интерфейс для всех событий
public interface IEvent { }

// Пример события - сообщение пользователя
public class UserCreatedEvent : IEvent
{
    public int UserId { get; }
    public string Email { get; }

    public UserCreatedEvent(int userId, string email)
    {
        UserId = userId;
        Email = email;
    }
}

Шаг 2: Создаём шину событий.

using System;
using System.Collections.Concurrent;
using System.Threading.Tasks;

public class EventBus
{
    // Хранилище подписчиков: ключ - тип события, значение - список обработчиков
    private readonly ConcurrentDictionary<Type, ConcurrentBag<object>> _subscribers = new();

    // Подписка на событие
    public void Subscribe<TEvent>(Func<TEvent, Task> handler) where TEvent : IEvent
    {
        var eventType = typeof(TEvent);
        var handlers = _subscribers.GetOrAdd(eventType, _ => new ConcurrentBag<object>());
        handlers.Add(handler);
    }

    // Отписка (упрощённая - удаляем все обработчики данного типа)
    public void Unsubscribe<TEvent>() where TEvent : IEvent
    {
        _subscribers.TryRemove(typeof(TEvent), out _);
    }

    // Публикация события (асинхронная)
    public async Task PublishAsync<TEvent>(TEvent eventData) where TEvent : IEvent
    {
        if (!_subscribers.TryGetValue(typeof(TEvent), out var handlers))
            return;

        foreach (var handler in handlers)
        {
            if (handler is Func<TEvent, Task> typedHandler)
            {
                await typedHandler(eventData);
            }
        }
    }
}

Шаг 3: Использование в приложении.

public class EmailService
{
    public async Task OnUserCreated(UserCreatedEvent evt)
    {
        Console.WriteLine($"Отправка приветственного письма на {evt.Email}");
        await Task.Delay(100); // Имитация отправки
    }
}

class Program
{
    static async Task Main(string[] args)
    {
        var bus = new EventBus();
        var emailService = new EmailService();

        // Подписка
        bus.Subscribe<UserCreatedEvent>(emailService.OnUserCreated);

        // Публикация события
        var userEvent = new UserCreatedEvent(1, "user@example.com");
        await bus.PublishAsync(userEvent);

        Console.WriteLine("Событие обработано.");
    }
}

Расширенная реализация: синглтон и синхронный режим

В реальных проектах часто используют <

Рекомендаций