DSoftStudio.Mediator 1.2.0

Prefix Reserved
dotnet add package DSoftStudio.Mediator --version 1.2.0
                    
NuGet\Install-Package DSoftStudio.Mediator -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DSoftStudio.Mediator" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DSoftStudio.Mediator" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="DSoftStudio.Mediator" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DSoftStudio.Mediator --version 1.2.0
                    
#r "nuget: DSoftStudio.Mediator, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DSoftStudio.Mediator@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DSoftStudio.Mediator&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=DSoftStudio.Mediator&version=1.2.0
                    
Install as a Cake Tool

DSoftStudio Mediator

NuGet NuGet Downloads CI Quality Gate License NativeAOT

A mediator that disappears in your pipeline.

A mediator with zero structural cost.

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

Designed for high-throughput, latency-sensitive systems where predictability matters.

Source-generated mediator for .NET.

  • Zero structural overhead — direct-call equivalent
  • Constant allocations — 72 B per Send (independent of pipeline depth)
  • Native AOT safe — no reflection or runtime codegen in any code path
  • Deterministic dispatch — no inheritance surprises, no duplicate handlers
  • MediatR-compatible API — drop-in migration

Zero overhead in real pipelines. Performance parity with direct calls — 667 ns vs 674 ns.

No surprises. No hidden cost. No runtime magic.


Why this exists

Most mediator implementations introduce hidden cost as systems grow — allocations scale with pipeline depth, execution flow becomes harder to trace, and tail latency becomes unpredictable.

This library removes those costs — by design.

What's different

  • Compile-time pipeline — no runtime composition
  • Fully inspectable execution — no hidden middleware chains
  • Constant allocation — independent of pipeline depth

Why this feels different

With traditional mediators:

  • you call Send()
  • the pipeline is assembled at runtime
  • the execution path is implicit

With DSoftStudio.Mediator:

  • the pipeline is compiled ahead of time
  • the execution is visible in generated code
  • the call is fully predictable

No hidden composition — everything is generated and visible. No runtime surprises.

For teams that care about:

  • Predictable latency (p99)
  • Minimal GC pressure
  • Debuggable pipelines
var result = await mediator.Send(new Ping()); // that's it

Quick Start →


Production validation

This library is not only benchmarked — it's validated under real-world conditions.

  • 2000+ parallel requests (Send / Publish)
  • Deep pipelines (6+ behaviors with retry, exceptions, async flows)
  • Failure injection (flaky handlers, retries, partial failures)
  • Chaos scenarios (random delays, intermittent faults)
  • Native AOT & trimming compatibility
  • Multi-project solutions with source generators

Full test catalog →


Why this matters in production

The real question

Does your mediator add cost to your pipeline?

Microbenchmark tables show framework overhead in isolation — nanoseconds that never exist alone in a real system. The meaningful question is: does the mediator add cost to your actual pipeline?

What we measured

A realistic enterprise pipeline — Validation → Logging → Metrics → async database write — with 3 pipeline behaviors and dependency injection. The kind of pipeline you ship to production.

Library Pipeline Latency Memory vs Direct Call
DSoftStudio.Mediator Direct call 674 ns 271 B
Mediator pipeline 667 ns 255 B 0.99×
DispatchR 2.1 Direct call 661 ns 271 B
Mediator pipeline 667 ns 255 B 1.01×
Mediator (Source Gen) 3.0 Direct call 679 ns 270 B
Mediator pipeline 718 ns 397 B 1.06×, 1.5× alloc
MediatR 14.1 Direct call 714 ns 270 B
Mediator pipeline 857 ns 1,032 B 1.20×, 3.8× alloc

The mediator layer adds zero measurable overhead. The cost is your handler — not the framework.

What this reveals

Four things that isolated microbenchmarks hide:

GC pressure compounds at scale. MediatR allocates 1,032 B per request in this pipeline. At 10k req/s, that's ~10 MB/s of short-lived Gen0 objects. DSoft allocates 255 B — the same as calling the method directly. Under sustained load, the difference shows up as GC pause frequency, not as nanoseconds in a benchmark table.

Allocation profile determines tail latency. More GC collections = more variance in p99/p999 response times. Constant-allocation pipelines produce tighter latency distributions. This matters more than mean latency in any SLA-bound system.

Pipeline depth shouldn't change your cost. DSoft allocates 72 B per Send whether you have 0, 3, or 5 behaviors — the allocation is constant because behaviors chain through interface dispatch, not delegate wrapping. MediatR allocates 272 B → 800 B → 1,088 B as you add behaviors, because each behavior wraps a new delegate and closure.

Implicit pipelines can become opaque as they grow. In MediatR, the behavior chain is assembled at runtime through service resolution. As systems grow, understanding the exact execution flow often requires tracing through middleware layers and the DI container. DSoftStudio.Mediator takes a different approach: the full pipeline is generated at compile time. The behavior chain is visible in source-generated code, inspectable in your IDE, and fully deterministic. What you register is what runs, in the order you registered it.

This is not about being faster in microbenchmarks — it's about keeping your system predictable and understandable as it scales.


What this means

In real systems, performance issues don't come from averages.

They come from:

  • GC pressure
  • tail latency (p99, p999)
  • unexpected allocations
  • failure scenarios

DSoftStudio.Mediator is designed to:

  • behave like a direct call
  • remain stable under load
  • avoid hidden runtime costs

Key guarantees

No runtime resolution All dispatch paths are source-generated. No IServiceProvider.GetService() on the hot path.
No hidden allocations Behavior chains use interface dispatch (IRequestHandler<,>), not Func<> delegates. No closures.
No reflection No MakeGenericType, Expression.Compile, or assembly scanning in any code path.
Compile-time pipeline transparency The full behavior chain is visible in generated code — inspectable, debuggable, and deterministic. No runtime assembly of middleware.
Deterministic notification dispatch Compile-time exact-type routing. Publishing DerivedEvent never invokes INotificationHandler<BaseEvent>.
AOT-safe by construction Structural property of the architecture, not a runtime guard or opt-in flag.
Constant-allocation pipeline 72 B per Send regardless of behavior count. Zero-alloc Publish.

Quick Start

dotnet add package DSoftStudio.Mediator
public record Ping() : IRequest<int>;

public class PingHandler : IRequestHandler<Ping, int>
{
    public ValueTask<int> Handle(Ping request, CancellationToken ct)
        => new ValueTask<int>(42);
}

Register at startup

Recommended — single-call registration (v1.2.0+)

services.AddMediator(builder =>
{
    // Register pipeline behaviors, processors, etc.
    builder.AddOpenBehavior(typeof(LoggingBehavior<,>));
    builder.AddRequestPreProcessor<ValidationPreProcessor>();
    builder.AddParallelNotificationPublisher();
});

No behaviors to register? You still need the builder callback — pass an empty lambda:

services.AddMediator(_ => { });

This registers handlers, precompiles pipelines, and freezes dispatch — all in one call.

Manual registration (v1.1.x style)

If you omit the builder callback, AddMediator() only registers core services (IMediator, ISender, IPublisher). You must chain the remaining steps yourself:

services.AddMediator()               // Core services only
    .RegisterMediatorHandlers()       // Discover and register all handlers
    .PrecompilePipelines();           // Build dispatch table and freeze

This is the v1.1.x pattern and remains fully supported for advanced scenarios where you need to insert registrations between steps.

Multi-project setup (hexagonal / clean architecture)

Install the full package only in the composition root (API / host). Application and domain layers reference only the abstractions:

# API / Host project (composition root) — source generator + DI registration
dotnet add package DSoftStudio.Mediator

# Application layer — contracts only (IRequest, IRequestHandler, IPipelineBehavior, etc.)
dotnet add package DSoftStudio.Mediator.Abstractions

The source generator runs in the API project and automatically discovers handlers from referenced assemblies. Handlers must be public — internal handlers require [InternalsVisibleTo] (see DSOFT005).

Host / API           → DSoftStudio.Mediator              (AddMediator + source generator)
Application          → DSoftStudio.Mediator.Abstractions  (handlers, requests, behaviors)
Domain               → (no mediator dependency)
Infrastructure       → (no mediator dependency)

Send a request

var result = await mediator.Send(new Ping());

Quick Start Guide · Installation


When to use this

Use DSoftStudio.Mediator when:

  • You need a mediator that adds zero overhead to your pipeline
  • Native AOT or trimming is required
  • Predictable p99 latency matters — high-throughput APIs, real-time systems
  • You want MediatR's API without MediatR's allocation profile
  • GC pressure is a concern at scale

Use MediatR when:

  • Performance is not a primary concern
  • You need runtime flexibility (dynamic handler discovery, inheritance-based notification dispatch)
  • Your team depends on MediatR's established ecosystem and community

This library is not:

  • A message bus — use MassTransit, NServiceBus, or Azure Service Bus
  • An event sourcing framework
  • A replacement for direct method calls when you don't need the mediator pattern

Comparison

Feature DSoft Mediator (SG) DispatchR MediatR
Structural overhead None Low Reduced High
Pipeline alloc overhead None +1.5× None +3.8×
Failure-tested
Chaos-tested
Concurrency-tested (2000+)
AOT-safe

Among the libraries benchmarked — Mediator (SG), DispatchR, and MediatR — DSoftStudio.Mediator is the only one validated for failure, chaos, and concurrency, with zero overhead in production pipelines.


Feature Comparison

Feature DSoft Mediator (SG) DispatchR MediatR
Source generators
Native AOT compatible
Reflection-free hot path
Zero-alloc pipeline
Auto-Singleton handlers
Self-handling requests
Exact-type notification dispatch
Runtime-typed Send(object)
Compile-time pipeline
MediatR-style API

Mental model

This mediator does not execute your pipeline.

It becomes your pipeline at compile time.


Execution Model

Send(request)
  → Precompiled pipeline (compile-time chain)
    → Behavior1 → Behavior2 → ... → BehaviorN
      → Handler (direct call, no GetService)
        → ValueTask<TResponse>

Publish(notification)
  → Closed dispatch table (compile-time, exact type)
    → Handler1, Handler2, ... → ValueTask (zero alloc)

No delegates. No closures. No IServiceProvider on the hot path. Every call is a direct typed invocation through a precompiled chain.


Features

Feature Description Docs
Pipeline Behaviors Zero-allocation chains via interface dispatch Docs
Pre/Post Processors Before/after hooks without chain responsibility Docs
CQRS ICommand<T> / IQuery<T> with semantic aliases Docs
Self-Handling Requests static Execute in request type — no handler class Docs
Notifications Exact-type compile-time dispatch Docs
Runtime Dispatch Send(object) via FrozenDictionary — AOT-safe Docs
Streams IAsyncEnumerable<T> with pipeline support Docs
Handler Validation ValidateMediatorHandlers() — fail fast at startup Docs
Native AOT Full AOT and trimming compatibility Docs

Ecosystem

ContractsDSoftStudio.Mediator.Abstractions · Reference from domain/application layers. No runtime dependency. Docs

ObservabilityDSoftStudio.Mediator.OpenTelemetry · Tracing + metrics for Send, Publish, and Stream dispatch paths. Docs

ValidationDSoftStudio.Mediator.FluentValidation · Automatic request validation via pipeline behavior. Docs

CachingDSoftStudio.Mediator.HybridCache · L1 + L2 response caching via Microsoft HybridCache. Docs


Design Notes

Notification dispatch by exact type

Notifications are dispatched by exact compile-time type, not by runtime inheritance hierarchy. Publishing a DerivedEvent that extends BaseEvent invokes only handlers registered for DerivedEventINotificationHandler<BaseEvent> is not invoked.

MediatR dispatches notifications via GetServicesAssignableTo, which walks the inheritance chain at runtime through reflection. This causes the well-known duplicate handler problem: a handler registered for a base type fires for every derived type, leading to unintended side effects that are difficult to diagnose.

DSoftStudio.Mediator avoids this entirely. The source generator emits a closed dispatch table at compile time — each notification type maps to exactly its registered handlers with no runtime type inspection. The result is deterministic dispatch with zero reflection overhead.

Additional design details

Interceptor code generation (Release vs Debug), mock safety, DSoftMediatorSuppressInterceptors kill switch, DSOFT004 analyzer, the recommended abstractions-only project pattern, and the NotificationPublisherFlag optimization.

Full Design Notes


Benchmarks (.NET 10)

Measured with BenchmarkDotNet on .NET 10. Each library runs in an isolated process to prevent cross-contamination. Compared against Mediator 3.0.1, DispatchR 2.1.1, and MediatR 14.1.

What matters

  • DSoft ≈ direct call in real pipelines
  • Constant allocation regardless of behavior count
  • No GC amplification under load

Latency

Operation DSoft Mediator (SG) DispatchR MediatR
Send() 7.2 ns 12.2 ns 33.4 ns 41.3 ns
Send() 5 behaviors 15.6 ns 36.8 ns 54.1 ns 153.1 ns
Publish() 4.5 ns 10.6 ns 35.7 ns 123.4 ns
CreateStream() 45.5 ns 44.7 ns 68.1 ns 122.9 ns
Cold Start 1.62 µs 9.91 µs 1.88 µs 3.24 µs

Allocations

Operation DSoft Mediator (SG) DispatchR MediatR
Send() 72 B 72 B 72 B 272 B
Send() 5 behaviors 72 B 72 B 72 B 1,088 B
Publish() 0 B 0 B 0 B 768 B
CreateStream() 232 B 232 B 232 B 624 B

Realistic Pipeline (Validation → Logging → Metrics → async DB)

Library Pipeline Latency Memory Overhead
DSoft Direct call 674 ns 271 B
Mediator pipeline 667 ns 255 B 0.99×
DispatchR Direct call 661 ns 271 B
Mediator pipeline 667 ns 255 B 1.01×
Mediator (SG) Direct call 679 ns 270 B
Mediator pipeline 718 ns 397 B 1.06×
MediatR Direct call 714 ns 270 B
Mediator pipeline 857 ns 1,032 B 1.20×

Full BenchmarkDotNet results: benchmarks/BENCHMARKS.md


Migrating from MediatR

Mechanical code changes. No architectural rewrite.

Concept MediatR DSoftStudio.Mediator
Handler return Task<TResponse> ValueTask<TResponse>
Behavior next RequestHandlerDelegate<T> IRequestHandler<TReq, TRes>
Calling next await next() await next.Handle(req, ct)
Pre/Post return Task ValueTask
Handler lifetime All Transient Stateless → Singleton
Namespace using MediatR; using DSoftStudio.Mediator.Abstractions;

Step-by-step Migration Guide


Samples

Sample Description Port
basic-api Query + Command, Minimal API 5100
pipeline-logging LoggingBehavior + ValidationBehavior 5200
domain-events INotification, multiple handlers 5300
streaming IAsyncEnumerable + SSE 5400
di-lifetimes Transient / Scoped / Singleton 5500
pre-post-processors Pre/Post processor hooks 5600
self-handling Self-handling with static Execute 5700
opentelemetry OTel tracing + metrics 5800
fluent-validation FluentValidation integration 5900
caching HybridCache integration 6000
mocking Expression tree detection + mocks
cross-project-mocking 3-project testability architecture
minimal-api Minimal API recipes (CRUD, pagination, auth) 6100
dotnet run --project samples/basic-api/DSoft.Sample.Api

Documentation

docs.dsoftstudio.com/mediator


A mediator should not be part of your performance budget.


Support

❤️ Sponsor on GitHub


License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (3)

Showing the top 3 NuGet packages that depend on DSoftStudio.Mediator:

Package Downloads
DSoftStudio.Mediator.OpenTelemetry

OpenTelemetry instrumentation for DSoftStudio.Mediator. Provides automatic distributed tracing and metrics for all mediator operations via standard pipeline behaviors.

DSoftStudio.Mediator.FluentValidation

FluentValidation integration for DSoftStudio.Mediator. Provides automatic request validation via a pipeline behavior that resolves IValidator<T> instances from DI and short-circuits on failure.

DSoftStudio.Mediator.HybridCache

HybridCache integration for DSoftStudio.Mediator. Provides automatic query caching via a pipeline behavior that leverages Microsoft.Extensions.Caching.Hybrid for multi-layer caching (L1 memory + L2 distributed), stampede prevention, and serialization.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 970 4/13/2026
1.1.8 393 3/23/2026
Loading failed