Event Bus and Observer Pattern in C# (.NET): Implementation for Microservices and GUI

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 Implementation



In modern C# (.NET) development, there is often a need to organize interaction between system components while maintaining their independence. The Observer pattern and its evolution — the Event Bus — are key tools for implementing loose coupling. In this article, we will break down how these mechanisms work and create a simple yet functional Event Bus implementation in C#.



Observer Pattern: The Classic Approach



The Observer pattern defines a "one-to-many" relationship between objects: when one object (the publisher) changes its state, all dependent objects (subscribers) are automatically notified. In .NET, this pattern is built in through events (event) and delegates.



Disadvantages of the classic implementation:

  • Direct dependency of the subscriber on the publisher (a reference to the object must be stored).
  • Complexity of managing subscriptions in large systems.
  • Threading issues (multithreading requires additional synchronization).


Event Bus: A Centralized Mediator



The Event Bus solves these problems by acting as a central mediator. Instead of subscribing directly to a specific object, components register with the event bus. Publishers publish events to the bus, and the bus distributes them to all subscribers. This implements the Pub/Sub (Publish/Subscribe) pattern.



Advantages of Event Bus:

  • Loose coupling: publishers and subscribers know nothing about each other.
  • Scalability: easy to add new subscribers.
  • Versatility: suitable for GUI (WPF/WinForms), microservices, and games (Unity).


Event Bus Implementation in C# (.NET)



We will create a basic Event Bus implementation that supports asynchronous processing and typed events. For this, we will use the IEvent interface and a generic EventBus class.



Step 1: Define the base types.

// Base interface for all eventspublic interface IEvent { }

// Example event - user messagepublic class UserCreatedEvent : IEvent{ public int UserId { get; } public string Email { get; }

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


Step 2: Create the event bus.

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

public class EventBus{ // Subscriber storage: key - event type, value - list of handlers private readonly ConcurrentDictionary<Type, ConcurrentBag<object>> _subscribers = new();

// Subscribe to an event 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); }

// Unsubscribe (simplified - removes all handlers of this type) public void Unsubscribe<TEvent>() where TEvent : IEvent { _subscribers.TryRemove(typeof(TEvent), out _); }

// Publish an event (asynchronous) 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); } } }}


Step 3: Usage in an application.

public class                    

Recommendations