Lightweight, drop-in replacement for MediatR’s core API
(IRequest, IMediator, INotification, IRequestHandler<>, IPipelineBehavior<>, Unit)
with native reflection-based scanning, deterministic pipeline order, and caching for hot paths.
dotnet add package Mediator.Compatusing Mediator.Compat;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMediatorCompat(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly); // scan handlers/notifications
cfg.AddOpenBehavior(typeof(LoggingBehavior<,>)); // pipeline (outer → inner)
});
var app = builder.Build();
app.MapGet("/ping", async (int x, IMediator mediator) =>
{
var result = await mediator.Send(new Ping(x));
return Results.Ok(result);
});
app.Run();
// ---- app code ----
public sealed record Ping(int X) : IRequest<int>;
public sealed class PingHandler : IRequestHandler<Ping, int>
{
public Task<int> Handle(Ping req, CancellationToken ct) => Task.FromResult(req.X + 1);
}
public sealed class LoggingBehavior<TReq,TRes> : IPipelineBehavior<TReq,TRes> where TReq : IRequest<TRes>
{
private readonly ILogger<LoggingBehavior<TReq, TRes>> _log;
public LoggingBehavior(ILogger<LoggingBehavior<TReq, TRes>> log) => _log = log;
public async Task<TRes> Handle(TReq request, RequestHandlerDelegate<TRes> next, CancellationToken ct)
{
_log.LogInformation("Handling {Request}", typeof(TReq).Name);
var res = await next();
_log.LogInformation("Handled {Request}", typeof(TReq).Name);
return res;
}
}AddMediatorCompat(...)registers IMediator and core internals and scans your assemblies for:IRequestHandler<TReq,TRes>INotificationHandler<TNote>
- Behaviors are not auto-registered. Add them explicitly to control order:
services.AddOpenBehavior(typeof(ValidationBehavior<,>));
- Order matters: registration order == execution order (first = outermost).
Caller → [Behavior #1] → [Behavior #2] → … → [Handler] → Result
↑ outer ↑ inner
- Behaviors may short-circuit (don’t call
next()), but use sparingly. CancellationTokenflows through every step.
Environment: macOS (Apple M4 Pro), .NET 8, BenchmarkDotNet DefaultJob.
Matrix:BehaviorCount ∈ {0, 2},NotificationHandlerCount ∈ {0, 2}.
Full raw output is captured indocs/benchmarks.md(CSV/table).
-
Send (Ping, 0 behaviors)
Mediator.Compat: 66.96 ns / 440 BMediatR: 61.25 ns / 336 B
→ MediatR is ~9% faster; Compat allocates +104 B more. (Baseline “no-pipeline” fast path is our next optimization target.)
-
Send (Ping, 2 behaviors)
Mediator.Compat: ~105 ns / 800 BMediatR: ~125 ns / 816 B
→ Compat is ~16–17% faster; −16 B less allocation. (Single-closure pipeline pays off.)
-
Send (Void, 0 behaviors)
Mediator.Compat: 59.31 ns / 296 BMediatR: 59.98 ns / 264 B
→ Essentially a tie on time; Compat is +32 B.
-
Send (Void, 2 behaviors)
Mediator.Compat: ~88–91 ns / 512 BMediatR: ~116 ns / 600 B
→ Compat is ~22–24% faster; −88 B less allocation.
-
Publish (Note, 0 handlers)
Mediator.Compat: 21.45 ns / 24 BMediatR: 35.05 ns / 88 B
→ Compat is ~39% faster; −64 B allocation.
-
Publish (Note, 2 handlers)
Mediator.Compat: ~36.9–40.2 ns / 144 BMediatR: ~78.2–79.6 ns / 464 B
→ Compat is ~2× faster with ~3× less allocation.
Run locally
dotnet run -c Release -p benchmarks/MediatorCompat.Benchmarks
- With pipeline behaviors enabled (≥1),
Mediator.Compatis consistently faster and allocates less. - For Publish, Compat leads both in time and allocation (sequential publish mode).
- In the baseline “no-behavior” Send case, MediatR is slightly ahead; we plan a dedicated no-pipeline fast path to close the gap.
- Introduce a no-behavior fast path (skip pipeline construction entirely).
- Cache handler factories (
Func<IServiceProvider, THandler>) to shave a few more nanoseconds off baseline. - Continue allocation dieting in baseline to approach ≤336 B.
See /docs/benchmarks.md for how to run.
- ✅ same core interfaces → easy swap.
- ✅ native reflection scanning with duplicate-registration guard.
- ✅ Deterministic behavior order (explicit; no accidental closed-type pickup).
- ✅ Delegate caching per
(TReq,TRes)forSend. - ✅ Single-closure pipeline to minimize per-call allocations.
- ✅ Auto-registration for open generic INotificationHandler<> and IRequestHandler<,>
- 🚧
Publishis sequential-only (parallel mode — backlog/idea).
- .NET 8.0+
- Nullable + analyzers recommended.
- Works great with Rider / VS / VS Code.
- NuGet: https://www.nuget.org/packages/Mediator.Compat
- GitHub: https://github.com/rabarba/Mediator.Compat
MIT © Ugur Kap