Event Bus and Observer Pattern in C# (.NET): Implementation and Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Event Bus and Observer Pattern in C# (.NET): From Theory to Practice



In modern C# (.NET) development, one of the key principles is loose coupling between system components. The Observer pattern and its evolution — the Event Bus — provide an elegant mechanism for organizing interaction between objects without direct dependencies. In this article, we will thoroughly examine how to implement an Event Bus in C# (.NET), review its architecture, and provide practical examples.



What is the Observer Pattern?

The Observer pattern defines a one-to-many dependency between objects: when one object (the subject) changes its state, all dependent objects (observers) are automatically notified and updated. In C#, this pattern is traditionally implemented using events (event) and delegates.



The Problem with the Classic Implementation

Although built-in C# events work well, they have limitations:

  • Direct coupling: the publisher must explicitly know the subscribers.
  • Unsubscription complexity: if a subscriber forgets to unsubscribe, a memory leak occurs.
  • Limited flexibility: it is difficult to organize asynchronous processing or event filtering.


What is an Event Bus?

An Event Bus is a centralized bus (mediator) that implements the Publisher-Subscriber pattern. Unlike the classic Observer, the publisher and subscriber do not know about each other at all. They communicate only through the bus:

  • Publisher sends a message (event) to the bus.
  • Bus finds all subscribers for this event type and notifies them.
  • Subscriber registers for a specific event type and handles it.


Advantages of Event Bus

  • Loose coupling: components do not depend on each other.
  • Scalability: easy to add new subscribers.
  • Testability: the bus can be replaced with mocks.
  • Asynchrony: events can be processed in background threads.


Implementing an Event Bus in C# (.NET)

Let's consider a simple but functional implementation. We will create an EventBus class that stores a dictionary of subscribers.



public interface IEventBus{    void Publish<TEvent>(TEvent eventData) where TEvent : class;    IDisposable Subscribe<TEvent>(Action<TEvent> handler) where TEvent : class;}

public class EventBus : IEventBus{ private readonly Dictionary<Type, List<Delegate>> _subscribers = new(); private readonly object _lock = new();

public void Publish<TEvent>(TEvent eventData) where TEvent : class { List<Delegate> handlers; lock (_lock) { if (!_subscribers.TryGetValue(typeof(TEvent), out var list)) return; handlers = new List<Delegate>(list); }

foreach (var handler in handlers) { if (handler is Action<TEvent> action) action(eventData); } }

public IDisposable Subscribe<TEvent>(Action<TEvent> handler) where TEvent : class { lock (_lock) { if (!_subscribers.ContainsKey(typeof(TEvent))) _subscribers[typeof(TEvent)] = new List<Delegate>(); _subscribers[typeof(TEvent)].Add(handler); }

return new Unsubscriber(() => { lock (_lock) { if (_subscribers.TryGetValue(typeof(TEvent), out var list)) list.Remove(handler); }

Recommendations