See More

A very common reason is a wrong site baseUrl configuration.\n

Current configured baseUrl = / (default value)\n

We suggest trying baseUrl = \n\n',document.body.prepend(n);var e=document.getElementById("__docusaurus-base-url-issue-banner-suggestion-container"),s=window.location.pathname,o="/"===s.substr(-1)?s:s+"/";e.textContent=o}document.addEventListener("DOMContentLoaded",function(){void 0===window.docusaurus&&insertBanner()})

Skip to main content

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, or IRequest for fire-and-forget operations that return Unit.
  • Notifications: Implement INotification to publish events to multiple handlers. Dispatch can be sequential, parallel, or polymorphic depending on configuration.
  • Streaming: Implement IStreamRequest<T> to receive an IAsyncEnumerable<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

GuideWhat you'll learn
Requests & HandlersCommands, queries, Unit responses
NotificationsEvents: sequential, parallel, polymorphic
StreamingIAsyncEnumerable for large result sets
Pipeline BehaviorsLogging, validation, caching middleware
Dependency InjectionDI containers, IMediator, factories
DiagnosticsZAM001–ZAM007 compiler error reference
PerformanceZero-alloc internals, benchmarks, AOT
Advanced PatternsError handling, cancellation, scoped behaviors
TestingUnit-test handlers, behaviors, and notifications