HIGH-PERFORMANCENATIVE AOTSOURCE GENERATORS

Zero structural overhead mediator
Compiled at build time.
Runs like a direct call.

No reflection. No runtime overhead.

01 · Architecture

A mediator that disappears
in your pipeline.

Four design decisions that keep DSoftStudio.Mediator within nanoseconds of a direct method call — even with five pipeline behaviors stacked on top.

2.7ns
per Send
0B
per Send · any depth
0
reflection
01 / 04Compile-time pipeline

No runtime composition.

Define a request, write a handler, register with one call. The source generator inspects your registrations and bakes the dispatch table at compile time — no reflection scan at startup, no expression trees, no delegate wrapping on the hot path.

  • Three steps from zero to handler
  • Real stack traces — no proxies, no DispatchProxy
  • AOT-safe by construction
Program.cs · quick start
1Define query + handler
public record Ping() : IQuery<int>;
 
public class PingHandler : IQueryHandler<Ping, int>
{
  public ValueTask<int> Handle(Ping request, CancellationToken ct)
    => new ValueTask<int>(42);
}
2Register · one call
services.AddMediator(_ => { });
// registers handlers, precompiles pipelines, freezes dispatch
3Send
var result = await mediator.Send(new Ping());
Setup
3 lines
Composition
At compile time
AOT
Safe
Realistic pipeline · ns/op · .NET 11↓ lower is better
Direct call
100 ns
DSoftStudio
112 ns
Mediator (SG)
238 ns
DispatchR
247 ns
MediatR 14
351 ns
1.12×
vs direct call
144 B
total alloc
7.1×
less than MediatR
02 / 04Memory profile

Constant allocation cost.

Nothing per Send on .NET 11, and the same figure at 0, 3 or 5 behaviors — depth changes the latency, never the allocation. On .NET 10 it is a flat 72 B, the async state machine that runtime async removes. Publish is zero-alloc on both. Constant allocation profiles give you tighter p99 / p999 latency under load.

  • 0 B / Send on .NET 11 · 72 B on .NET 10 · 0 B / Publish
  • No delegate and no closure per behavior
  • Validated with BenchmarkDotNet · .NET 10 and .NET 11
03 / 04Determinism

Notifications by exact compile-time type.

The source generator emits a closed dispatch table. Publishing DerivedEvent never invokes INotificationHandler<BaseEvent>. No reflection walk. No duplicate-handler problem.

  • Closed dispatch table at compile time
  • No accidental base-type fan-out
  • Production-validated · 2000+ parallel requests
NotificationDispatch.g.cs · source-generated
// closed switch table — exact-type routing, no inheritance walk
switch (notification)
{
case UserRegistered u: return Publish<UserRegistered>(u, ct);
case OrderPlaced p: return Publish<OrderPlaced>(p, ct);
case BaseEvent: // never emitted — MediatR would fan out here
}
✓ Deterministic · 0 B allocated · 2.4 ns
PingHandler.cs · diff
-using MediatR;
- public Task<int> Handle(Ping req, CancellationToken ct)
+using DSoftStudio.Mediator.Abstractions;
+using DSoftStudio.Mediator;
+ public ValueTask<int> Handle(Ping req, CancellationToken ct)
Lifetime
→ Singleton
Behavior next
IRequestHandler
Effort
Low
04 / 04Migration

Straightforward migration.

IRequest, INotification and IStreamRequest markers are identical. Handlers swap Task for ValueTask, namespaces change, and registration moves to AddMediator. Pipeline behaviors need a small refactor — next() becomes next.Handle(request, ct). Your architecture stays.

  • Same Send / Publish call sites
  • Task → ValueTask in handlers · namespace swap
  • Handler lifetimes derived from the constructor
  • Step-by-step migration guide on docs.dsoftstudio.com
02 · Real-world benchmark

The cost of your pipeline should be
your code — not your mediator.

Microbenchmarks measure framework overhead in isolation. We measured something more honest: a real enterprise pipeline — Validation → Logging → Metrics → async DB write — across four mediator libraries, each measured against its own direct call so the comparison is what the mediator added, not whose machine was faster.

1.12×overhead
vs direct call · 3-behavior pipeline · .NET 11
112 ns through the mediator. 100 ns calling the handler directly.
Library
Direct call
Mediator pipeline
Overhead
DSoftStudio.Mediator
100 ns
168 B
112 ns
144 B
1.12×
0.86× alloc
Mediator (SG) 3.0
97 ns
168 B
238 ns
335 B
2.46×
2.0× alloc
DispatchR 2.3
100 ns
168 B
247 ns
263 B
2.47×
1.6× alloc
MediatR 14.2
99 ns
168 B
351 ns
1,016 B
3.56×
6.0× alloc
* BenchmarkDotNet · .NET 11 · isolated processes · pipeline: Validation → Logging → Metrics → async DB write. Full results: .NET 11 · .NET 10.
Pipeline depth scalingSend · isolated microbench
↓ lower is better
DSoftStudio
no behaviors2.5 ns
3 behaviors5.6 ns
5 behaviors6.6 ns
0 → 5 scale: 2.7×
Mediator (SG)
no behaviors10.5 ns
3 behaviors19.9 ns
5 behaviors26.5 ns
0 → 5 scale: 2.5×
DispatchR
no behaviors26.7 ns
3 behaviors32.0 ns
5 behaviors31.8 ns
0 → 5 scale: 1.2×
MediatR
no behaviors41.2 ns
3 behaviors104.6 ns
5 behaviors136.1 ns
0 → 5 scale: 3.3×
With 5 behaviors stacked, DSoftStudio is 20× faster than MediatR (6.6 ns vs 136.1 ns) — and still allocates nothing.
01

GC pressure compounds at scale.

DSoft allocates 144 B per request in this pipeline — no more than calling the handler directly, which measures 168 B in the same benchmark. MediatR allocates 1,016 B for the same work; at 10k req/s that is ~8.7 MB/s of short-lived Gen0 garbage you weren't budgeting for.

02

Allocation profile shapes tail latency.

More GC collections mean more variance in p99 / p999. Constant-allocation pipelines produce tighter distributions — and tighter distributions are what SLAs are written against.

03

Pipeline depth shouldn't change cost.

DSoft allocates nothing per Send whether you have 0, 3 or 5 behaviors. MediatR wraps each behavior in a new delegate and closure, so allocation grows with depth: 248 B at zero behaviors and 1,024 B at five — a far hotter Gen0 path for the same handler.

04

Implicit pipelines become opaque.

DSoft emits the full behavior chain at compile time — visible in the source-generated code, inspectable in your IDE, fully deterministic. Mediators that assemble the chain at runtime through service resolution hide the order: behavior interactions become inferred rather than read.

Mental model

This mediator does not execute your pipeline.

It becomes your pipeline at compile time.

2000+
Parallel requests tested
6+
Behaviors deep · nesting order asserted
0
IL warnings under Native AOT
0
Reflection on hot path
03 · Ship

No surprises.
No hidden cost.
No runtime magic.

A source-generated mediator for .NET 8 through .NET 11. Drop it into your composition root and keep your existing handlers.

2.7ns
per Send
2.4ns
per Publish · 0 B
0B
per Send · any depth
0
IL warnings under Native AOT
~/your-app · zsh
.NET 11
$ dotnet add package DSoftStudio.Mediator
Determining projects to restore...
info : package 'DSoftStudio.Mediator' version '1.4.0' added to 'YourApp.csproj'.
log : Restored YourApp.csproj (in 824 ms).
// Program.cs
services.AddMediator(builder => {
builder.AddOpenBehavior(typeof(LoggingBehavior<,>));
});
$ dotnet run
▸ DSoftStudio.Mediator: generated 4 dispatchers · 0 ms
● Listening on http://localhost:5000
dotnet add package DSoftStudio.Mediator