Getting Started
ZeroAlloc.Mediator is a zero-allocation, compile-time-dispatched mediator library for .NET 8 and .NET 10 that implements the mediator pattern without runtime reflection. Rather than using a dictionary lookup at runtime, it leverages a Roslyn source generator to emit strongly-typed dispatch methods at build time, making it fully Native AOT-compatible. Benchmarks show it is 26–65x faster than MediatR on request/response paths, with zero heap allocations on synchronous paths.
Installation
dotnet add package ZeroAlloc.Mediator
No extra setup is required. The source generator runs automatically as part of your normal dotnet build and emits all dispatch code into your project.
Your First Request
The following example walks through placing an order to illustrate the full request/handler/send cycle.
Step 1 — Define the request
Requests must be readonly record struct types (not classes). The type implements IRequest<TResponse> where TResponse is the value returned by the handler.
using ZeroAlloc.Mediator;
public readonly record struct PlaceOrderCommand(
string CustomerId,
IReadOnlyList<OrderLineItem> Items
) : IRequest<OrderId>;
public readonly record struct OrderId(Guid Value);
public readonly record struct OrderLineItem(string Sku, int Quantity, decimal UnitPrice);
Step 2 — Implement the handler
Create a class that implements IRequestHandler<TRequest, TResponse>. The handler receives the request and a CancellationToken, and returns a ValueTask<TResponse>.
public class PlaceOrderHandler : IRequestHandler<PlaceOrderCommand, OrderId>
{
public ValueTask<OrderId> Handle(PlaceOrderCommand command, CancellationToken ct)
{
// In a real app, persist the order here
var id = new OrderId(Guid.NewGuid());
return ValueTask.FromResult(id);
}
}
Step 3 — Send the request
Call the static Mediator.Send method. The overload is generated specifically for PlaceOrderCommand, so the call is fully typed — no casting, no boxing.
var command = new PlaceOrderCommand("customer-42", [
new OrderLineItem("SKU-001", 2, 29.99m)
]);
OrderId orderId = await Mediator.Send(command);
Console.WriteLine($"Order placed: {orderId.Value}");
Step 4 — Build
Just run dotnet build. The source generator emits the Mediator.Send overload for PlaceOrderCommand automatically — nothing else to configure.
What Gets Generated
After building, the source generator emits a strongly-typed overload for every request type it discovers. For PlaceOrderCommand the output looks roughly like this (illustration only):
// Auto-generated by ZeroAlloc.Mediator.Generator
public static partial class Mediator
{
public static ValueTask<OrderId> Send(
PlaceOrderCommand request, CancellationToken ct = default)
=> (_placeOrderHandlerFactory?.Invoke() ?? new PlaceOrderHandler())
.Handle(request, ct);
}
One strongly-typed overload is generated per request type. There is no dictionary lookup and no reflection involved at any point in the dispatch path.
Architecture Overview
The sequence below shows how a Send call flows from your application code through the generated mediator to the handler and back.
Key Concepts
- Requests: Implement
IRequest<TResponse>for request/response interactions, orIRequestfor fire-and-forget operations that returnUnit. - Notifications: Implement
INotificationto publish events to multiple handlers. Dispatch can be sequential, parallel, or polymorphic depending on configuration. - Streaming: Implement
IStreamRequest<T>to receive anIAsyncEnumerable<T>result, suited for large or paginated result sets. - Pipeline Behaviors: Apply cross-cutting concerns such as logging, validation, or caching by decorating a class with the
[PipelineBehavior]attribute. - Zero allocation: Always declare request types as
readonly record struct. Using a class triggers compiler diagnostic ZAM003 and forfeits the zero-allocation guarantee.
Next Steps
| Guide | What you'll learn |
|---|---|
| Requests & Handlers | Commands, queries, Unit responses |
| Notifications | Events: sequential, parallel, polymorphic |
| Streaming | IAsyncEnumerable for large result sets |
| Pipeline Behaviors | Logging, validation, caching middleware |
| Dependency Injection | DI containers, IMediator, factories |
| Diagnostics | ZAM001–ZAM007 compiler error reference |
| Performance | Zero-alloc internals, benchmarks, AOT |
| Advanced Patterns | Error handling, cancellation, scoped behaviors |
| Testing | Unit-test handlers, behaviors, and notifications |