DSoftStudio Mediator

← Back to Documentation

Runtime-Typed Dispatch (Send(object))

For message bus, command queue, and event sourcing scenarios where the consumer deserializes a request from the wire and only has an object reference at runtime:

// Producer — serialize command + type
var command = new CreateUser("alice@example.com");
queue.Push((JsonSerializer.Serialize(command), command.GetType().AssemblyQualifiedName));

// Consumer — deserialize and dispatch without knowing TResponse
(string raw, string typeName) = queue.Pop();
var type = Type.GetType(typeName)!;
var command = JsonSerializer.Deserialize(raw, type)!;

var result = await mediator.Send(command); // runtime-typed dispatch → ValueTask<object?>

The Send(object) overload compiles to a generated type switch over every request type the compilation can see. No reflection, no MakeGenericType — the dispatch is ordinary C# the compiler checks.

How It Works

Each case body lives in its own method rather than inside the switch. That is deliberate: inlined, the switch grew about 1.2 KB of machine code per request type, and past nine types it stopped fitting the JIT’s inlining budget — Send(object) went from 5.5 ns to 9.5 ns on a single added type, and kept climbing to 20 ns by eighty. Outlined, it stays flat.

Overload Resolution

Send(object) is implemented as an extension method (not an interface method). This ensures the generated typed extension methods are always preferred when the compile-time type is known:

// Typed extension wins — zero-overhead, no boxing
var result = await mediator.Send(new Ping()); // → ValueTask<int>

// Object extension — runtime dispatch, response boxed
object request = new Ping();
var result = await mediator.Send(request);   // → ValueTask<object?>

Performance

Measured on .NET 11; .NET 10 is roughly twice as slow on both rows.

Path Cost Boxing Use case
Send(new Ping()) 2.7 ns None Normal application code
Send((object)ping) 6.6 ns TResponse → object? Queue/bus consumers

About 4 ns and one boxed response, against deserialization costs measured in microseconds on the paths that actually need this overload. Not a reason to avoid it where it fits.