SkillAgentSearch skills...

DispatchR

Fast, zero-alloc alternative to MediatR for .NET – minimal, blazing fast, and DI-friendly.

Install / Use

/learn @hasanxdev/DispatchR
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<img src="https://github.com/hasanxdev/DispatchR/raw/main/icon-mini.png" width="26">ispatchR 🚀

CI codecov NuGet NuGet

A High-Performance Mediator Implementation for .NET :trollface:

** Minimal memory footprint. Blazing-fast execution. **

[!NOTE] If you're curious to see the power of this library, check out the benchmark comparing MediatR vs Mediator Source Generator vs DispatchR.

⚡ Key Features

  • Built entirely on top of Dependency Injection
  • Zero runtime reflection after registration
  • Choose your handler return type: Task, ValueTask, or Synchronous Method
  • Allocates nothing on the heap — ideal for high-throughput scenarios
  • Outperforms existing solutions in most real-world benchmarks
  • Seamlessly compatible with MediatR — migrate with minimal effort
  • Include or exclude a set of handlers from an assembly — ideal for use with Aspire
  • Currently supports
    1. Simple Request:
      1. IRequest<TRquest, TResponse>
      2. IRequestHandler<TRequest, TResponse>
      3. IPipelineBehavior<TRequest, TResponse>
    2. Stream Request:
      1. IStreamRequest<TRquest, TResponse>
      2. IStreamRequestHandler<TRequest, TResponse>
      3. IStreamPipelineBehavior<TRequest, TResponse>
    3. Notifications:
      1. INotification
      2. INotificationHandler<TRequestEvent>

:bulb: Tip: If you're looking for a mediator with the raw performance of hand-written code, DispatchR is built for you.

✨ How to install?

dotnet add package DispatchR.Mediator

You can also separately add only the abstractions, which include the interfaces, in another layer:

dotnet add package DispatchR.Mediator.Abstractions

Syntax Comparison: DispatchR vs MediatR

In the following, you will see the key differences and implementation details between MediatR and DispatchR.

Request Definition

MediatR

public sealed class PingMediatR : IRequest<int> { }

DispatchR

  1. Sending TRequest to IRequest
  2. Precise selection of output for both async and sync handlers
    1. Ability to choose between Task and ValueTask
public sealed class PingDispatchR : IRequest<PingDispatchR, ValueTask<int>> { } 

Handler Definition

MediatR

public sealed class PingHandlerMediatR : IRequestHandler<PingMediatR, int>
{
    public Task<int> Handle(PingMediatR request, CancellationToken cancellationToken)
    {
        return Task.FromResult(0);
    }
}

DispatchR (Don't change)

public sealed class PingHandlerDispatchR : IRequestHandler<PingDispatchR, ValueTask<int>>
{
    public ValueTask<int> Handle(PingDispatchR request, CancellationToken cancellationToken)
    {
        return ValueTask.FromResult(0);
    }
}

Pipeline Behavior

MediatR

public sealed class LoggingBehaviorMediat : IPipelineBehavior<PingMediatR, int>
{
    public Task<int> Handle(PingMediatR request, RequestHandlerDelegate<int> next, CancellationToken cancellationToken)
    {
        return next(cancellationToken);
    }
}

DispatchR

  1. Use Chain of Responsibility pattern
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);
    }
}

Generic pipeline behavior DispatchR

  1. For every kind of return type — Task, ValueTask, or synchronous methods — you need to write a generic pipeline behavior. However, you don't need a separate pipeline for each request. As shown in the code below, this is a GenericPipeline for requests that return a ValueTask.
public class GenericPipelineBehavior<TRequest, TResponse>() : IPipelineBehavior<TRequest, ValueTask<TResponse>>
    where TRequest : class, IRequest<TRequest, ValueTask<TResponse>>
{
    public required IRequestHandler<TRequest, ValueTask<TResponse>> NextPipeline { get; set; }
    
    public ValueTask<TResponse> Handle(TRequest request, CancellationToken cancellationToken)
    {
        // You can add custom logic here, like logging or validation
        // This pipeline behavior can be used for any request type
        return NextPipeline.Handle(request, cancellationToken);
    }
}

Summary

  • DispatchR lets the request itself define the return type.
  • No runtime reflection in DispatchR — it's optimized for performance.
  • No static behavior chains — pipelines are chained via DI and handler wiring.
  • Supports void, Task, or ValueTask as return types.

Ideal for high-performance .NET applications.

Stream Request Definition

MediatR Stream

public sealed class CounterStreamRequestMediatR : IStreamRequest<int> { }

DispatchR

  1. Sending TRequest to IStreamRequest
public sealed class CounterStreamRequestDispatchR : IStreamRequest<PingDispatchR, ValueTask<int>> { } 

Stream Handler Definition

Stream Handler MediatR

public sealed class CounterStreamHandlerMediatR : IStreamRequestHandler<CounterStreamRequestMediatR, int>
{
    public async IAsyncEnumerable<int> Handle(CounterStreamRequestMediatR request, CancellationToken cancellationToken)
    {
        yield return 1;
    }
}

Stream Handler DispatchR (Don't change)

public sealed class CounterStreamHandlerDispatchR : IStreamRequestHandler<CounterStreamHandlerDispatchR, int>
{
    public async IAsyncEnumerable<int> Handle(CounterStreamHandlerDispatchR request, CancellationToken cancellationToken)
    {
        yield return 1;
    }
}

Stream Pipeline Behavior

Stream Pipeline MediatR

public sealed class CounterPipelineStreamHandler : IStreamPipelineBehavior<CounterStreamRequestMediatR, string>
{
    public async IAsyncEnumerable<string> Handle(CounterStreamRequestMediatR request, StreamHandlerDelegate<string> next, [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        await foreach (var response in next().WithCancellation(cancellationToken).ConfigureAwait(false))
        {
            yield return response;
        }
    }
}

Stream Pipeline DispatchR

  1. Use Chain of Responsibility pattern
public sealed class CounterPipelineStreamHandler : IStreamPipelineBehavior<CounterStreamRequestDispatchR, string>
{
    public required IStreamRequestHandler<CounterStreamRequestDispatchR, string> NextPipeline { get; set; }
    
    public async IAsyncEnumerable<string> Handle(CounterStreamRequestDispatchR request, [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        await foreach (var response in NextPipeline.Handle(request, cancellationToken).ConfigureAwait(false))
        {
            yield return response;
        }
    }
}

Generic stream pipeline behavior DispatchR

public class GenericStreamPipelineBehavior<TRequest, TResponse>() : IStreamPipelineBehavior<TRequest, TResponse>
    where TRequest : class, IStreamRequest<TRequest, TResponse>
{
    public IStreamRequestHandler<TRequest, TResponse> NextPipeline { get; set; }
    
    public async IAsyncEnumerable<TResponse> Handle(TRequest request, CancellationToken cancellationToken)
    {
        await foreach (var response in NextPipeline.Handle(request, cancellationToken).ConfigureAwait(false))
        {
            yield return response;
        }
    }
}

Notification

Notification MediatR

public sealed record Event(Guid Id) : INotification;

public sealed class EventHandler(ILogger<Event> logger) : INotificationHandler<Event>
{
    public Task Handle(Event notification, CancellationToken cancellationToken)
    {
        logger.LogInformation("Received notification");
        return Task.CompletedTask;
    }
}

Stream Pipeline DispatchR

  1. Use ValueTask
public sealed record Event(Guid Id) : INotification;

public sealed class EventHandler(ILogger<Event> logger) : INotificationHandler<Event>
{
    public ValueTask Handle(Event notification, CancellationToken cancellationToken)
    {
        logger.LogInformation("Received notification");
        return ValueTask.CompletedTask;
    }
}

⚡ How DispatchR Achieves High Performance

DispatchR is designed with one goal in mind: maximize performance with minimal memory usage. Here's how it accomplishes that:

What Happens Inside the Send Method?

public TResponse Send<TRequest, TResponse>(IRequest<TRequest, TResponse> request,
    CancellationToken cancellationToken) where TRequest : class, IRequest
{
    return serviceProvider
        .GetRequiredService<IRequestHandler<TRequest, TResponse>>()
        .Handle(Unsafe.As<TRequest>(request), cancellationToken);
}

What Happens Inside the CreateStream Method?

public IAsyncEnumerable<TResponse> CreateStream<TRequest, TResponse>(IStreamRequest<TRequest, TResponse> request, 
        CancellationToken cancellationToken) where TRequest : class, IStreamRequest
{
    return serviceProvider.GetRequiredService<IStreamRequestHandler<TRequest, TResponse>>()
        .Handle(Unsafe.As<TRequest>(request), cancellationToken);
}

Only the handler is resolved and directly invoked!

What Happens Inside the Publish Method?

View on GitHub
GitHub Stars382
CategoryDevelopment
Updated22h ago
Forks33

Languages

C#

Security Score

100/100

Audited on Mar 26, 2026

No findings