BehavioralC#verifiedVerified
Mediator Pattern in C#
Defines an object that encapsulates how a set of objects interact, promoting loose coupling by keeping objects from referring to each other explicitly.
How to Implement the Mediator Pattern in C#
1Step 1: Define the mediator interface
public interface IMediator
{
void Send(string message, Colleague sender);
}2Step 2: Colleague base class communicates via mediator
public abstract class Colleague(IMediator mediator)
{
protected IMediator Mediator => mediator;
public abstract void Receive(string message);
}3Step 3: Concrete colleagues
public class UserChat(IMediator mediator, string name)
: Colleague(mediator)
{
public string Name => name;
public void Send(string message) =>
Mediator.Send(message, this);
public override void Receive(string message) =>
Console.WriteLine($"{Name} received: {message}");
}4Step 4: Concrete mediator routes messages
public class ChatRoom : IMediator
{
private readonly List<UserChat> _users = [];
public void Register(UserChat user) => _users.Add(user);
public void Send(string message, Colleague sender)
{
foreach (var user in _users)
if (user != sender)
user.Receive(message);
}
}using System.Collections.Concurrent;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
// [step] Define CQRS-style mediator with request/response
public interface IRequest<TResponse> { }
public interface IRequestHandler<in TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
Task<TResponse> HandleAsync(TRequest request,
CancellationToken ct = default);
}
public interface INotification { }
public interface INotificationHandler<in TNotification>
where TNotification : INotification
{
Task HandleAsync(TNotification notification,
CancellationToken ct = default);
}
// [step] Pipeline behavior for cross-cutting concerns
public interface IPipelineBehavior<in TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
Task<TResponse> HandleAsync(
TRequest request,
Func<Task<TResponse>> next,
CancellationToken ct = default);
}
// [step] Production mediator with DI integration
public sealed class Mediator(
IServiceProvider services, ILogger<Mediator> logger) : IDisposable
{
private readonly ConcurrentDictionary<Type, object> _handlerCache = new();
public async Task<TResponse> SendAsync<TResponse>(
IRequest<TResponse> request, CancellationToken ct = default)
{
var requestType = request.GetType();
var handlerType = typeof(IRequestHandler<,>)
.MakeGenericType(requestType, typeof(TResponse));
var handler = services.GetService(handlerType)
?? throw new InvalidOperationException(
$"No handler for {requestType.Name}");
logger.LogDebug("Dispatching {Request}", requestType.Name);
// Build pipeline
var pipelineType = typeof(IPipelineBehavior<,>)
.MakeGenericType(requestType, typeof(TResponse));
var pipelines = services.GetService(
typeof(IEnumerable<>).MakeGenericType(pipelineType));
// Execute via dynamic dispatch
var method = handlerType.GetMethod("HandleAsync")!;
return (TResponse)(await (Task<TResponse>)method.Invoke(
handler, [request, ct])!)!;
}
public async Task PublishAsync<TNotification>(
TNotification notification, CancellationToken ct = default)
where TNotification : INotification
{
var handlers = services
.GetService<IEnumerable<INotificationHandler<TNotification>>>();
if (handlers is null) return;
var tasks = handlers.Select(async h =>
{
try
{
await h.HandleAsync(notification, ct);
}
catch (Exception ex)
{
logger.LogError(ex,
"Notification handler failed for {Type}",
typeof(TNotification).Name);
}
});
await Task.WhenAll(tasks);
}
public void Dispose() => _handlerCache.Clear();
}
// [step] Logging pipeline behavior example
public class LoggingBehavior<TRequest, TResponse>(
ILogger<LoggingBehavior<TRequest, TResponse>> logger)
: IPipelineBehavior<TRequest, TResponse>
where TRequest : IRequest<TResponse>
{
public async Task<TResponse> HandleAsync(
TRequest request, Func<Task<TResponse>> next,
CancellationToken ct = default)
{
var name = typeof(TRequest).Name;
logger.LogInformation("Handling {Request}", name);
var sw = System.Diagnostics.Stopwatch.StartNew();
var response = await next();
logger.LogInformation("Handled {Request} in {Duration}ms",
name, sw.ElapsedMilliseconds);
return response;
}
}Mediator Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Mediator Pattern in the Real World
“An air traffic control tower is the classic example. Instead of every plane communicating directly with every other plane—a chaotic and dangerous mess—all aircraft talk only to the control tower. The tower mediates all interactions, directing each plane based on the overall picture it maintains. Planes are decoupled from each other entirely.”