A fast, zero-alloc Mediator pattern alternative to MediatR in .NET – minimal, blazing fast, and DI-friendly (Dependency Injection).
License
—
Deps
4
Install Size
—
Vulns
✓ 0
Published
Oct 8, 2025
$ dotnet add package DispatchR.Mediator** Minimal memory footprint. Blazing-fast execution. **
Task, ValueTask, or Synchronous Method:bulb: Tip: If you're looking for a mediator with the raw performance of hand-written code, DispatchR is built for you.
public sealed class PingMediatR : IRequest<int> { }
TRequest to IRequestasync and sync handlers
Task and ValueTaskpublic sealed class PingDispatchR : IRequest<PingDispatchR, ValueTask<int>> { }
public sealed class PingHandlerMediatR : IRequestHandler<PingMediatR, int>
{
public Task<int> Handle(PingMediatR request, CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
}
public sealed class PingHandlerDispatchR : IRequestHandler<PingDispatchR, ValueTask<int>>
{
public ValueTask<int> Handle(PingDispatchR request, CancellationToken cancellationToken)
{
return ValueTask.FromResult(0);
}
}
public sealed class LoggingBehaviorMediat : IPipelineBehavior<PingMediatR, int>
{
public Task<int> Handle(PingMediatR request, RequestHandlerDelegate<int> next, CancellationToken cancellationToken)
{
return next(cancellationToken);
}
}
public sealed class LoggingBehaviorDispatchR : IPipelineBehavior<PingDispatchR, ValueTask<int>>
{
public required IRequestHandler<PingDispatchR, ValueTask<int>> NextPipeline { get; set; }
public ValueTask<int> Handle(PingDispatchR request, CancellationToken cancellationToken)
{
return NextPipeline.Handle(request, cancellationToken);
}
}
void, Task, or ValueTask as return types.Ideal for high-performance .NET applications.
Send Method?public TResponse Send<TRequest, TResponse>(IRequest<TRequest, TResponse> request,
CancellationToken cancellationToken) where TRequest : class, IRequest, new()
{
return serviceProvider
.GetRequiredService<IRequestHandler<TRequest, TResponse>>()
.Handle(Unsafe.As<TRequest>(request), cancellationToken);
}
✅ Only the handler is resolved and directly invoked!
But the real magic happens behind the scenes when DI resolves the handler dependency:
💡 Tips: We cache the handler using DI, so in scoped scenarios, the object is constructed only once and reused afterward.
services.AddScoped(handlerInterface, sp =>
{
var pipelines = sp
.GetServices(pipelinesType)
.Select(s => Unsafe.As<IRequestHandler>(s)!);
IRequestHandler lastPipeline = Unsafe.As<IRequestHandler>(sp.GetService(handler))!;
foreach (var pipeline in pipelines)
{
pipeline.SetNext(lastPipeline);
lastPipeline = pipeline;
}
return lastPipeline;
});
✨ This elegant design chains pipeline behaviors at resolution time — no static lists, no reflection, no magic.
Of course, our goal is to stay dependency-free — but for now, I think it's totally fine to rely on this as a starting point!
It's simple! Just use the following code:
builder.Services.AddDispatchR(typeof(MyCommand).Assembly);
This code will automatically register all pipelines by default. If you need to register them in a specific order, you can either add them manually or write your own reflection logic:
builder.Services.AddDispatchR(typeof(MyCommand).Assembly, withPipelines: false);
builder.Services.AddScoped<IPipelineBehavior<MyCommand, int>, PipelineBehavior>();
builder.Services.AddScoped<IPipelineBehavior<MyCommand, int>, ValidationBehavior>();
dotnet add package DispatchR.Mediator --version 1.0.0
[!IMPORTANT] This benchmark was conducted using MediatR version 12.5.0 and the stable release of Mediator Source Generator, version 2.1.7. Version 3 of Mediator Source Generator was excluded due to significantly lower performance.


We welcome contributions to make this package even better! ❤️
Let's build something amazing together! 🚀