Reputation: 135
I want to implement a CommandBus
that can Dispatch
some Commands
to CommandHandlers
.
Command
is a simple a DTO describing what should happen. For instance : "Increment counter by 5"CommandHandler
is able to handle a precise type of Command
.CommandBus
takes a Command
and executes the CommandHandler
that is able to handle it.The code I wrote does not compile.
Compiler complains cannot convert from 'IncrementHandler' to 'Handler<Command>'
.
I don't understand why, because IncrementHandler
implements Handler<Increment>
and Increment
implements Command
I've tried both in
and out
modifiers on the generic interfaces, it doesn't solve the problem.
Is there a way to achieve this with only interfaces ?
[TestClass]
public class CommandBusTest
{
[TestMethod]
public void DispatchesProperly()
{
var handler = new IncrementHandler(counter: 0);
var bus = new CommandBus(handler); // <--Doesn't compile: cannot convert from 'IncrementHandler' to 'Handler<Command>'
bus.Dispatch(new Increment(5));
Assert.AreEqual(5, handler.Counter);
}
}
public class CommandBus
{
private readonly Dictionary<Type, Handler<Command>> handlers;
public CommandBus(params Handler<Command>[] handlers)
{
this.handlers = handlers.ToDictionary(
h => h.HandledCommand,
h => h);
}
public void Dispatch(Command commande) { /*...*/ }
}
public interface Command { }
public interface Handler<TCommand> where TCommand : Command
{
Type HandledCommand { get; }
void Handle(TCommand command);
}
public class Increment : Command
{
public Increment(int value) { Value = value; }
public int Value { get; }
}
public class IncrementHandler : Handler<Increment>
{
// Handler<Increment>
public Type HandledCommand => typeof(Increment);
public void Handle(Increment command)
{
Counter += command.Value;
}
// Handler<Increment>
public int Counter { get; private set; }
public IncrementHandler(int counter)
{
Counter = counter;
}
}
Upvotes: 4
Views: 382
Reputation: 660030
I don't understand why, because
IncrementHandler
implementsHandler<Increment>
andIncrement
implementsCommand
Let's fix your misunderstanding, and then the rest will become clear.
Suppose what you wanted to do was legal. What goes wrong?
IncrementHandler ih = whatever;
Handler<Command> h = ih; // This is illegal. Suppose it is legal.
now we make a class
public class Decrement : Command { ... }
And now we pass it to h:
Decrement d = new Decrement();
h.Handle(d);
This is legal, because Handler<Command>.Handle
takes a Command
, and a Decrement
is a Command
.
So what happened? You just passed a decrement command to ih
, via h
, but ih
is an IncrementHandler
that only knows how to handle increments.
Since that is nonsensical, something in here has to be illegal; which line would you like to be illegal? The C# team decided that the conversion is the thing that should be illegal.
More specifically:
Your program is using reflection in an attempted end-run around the type system's safety checks, and then you are complaining that the type system is stopping you when you write something unsafe. Why are you using generics at all?
Generics are (in part) to ensure type safety, and then you are doing a dispatch based on reflection. This doesn't make any sense; don't take steps to increase type safety and then do heroic efforts to work around them.
Plainly you wish to work around type safety, so don't use generics at all. Just make an ICommand
interface and a Handler
class that takes a command, and then have some mechanism for working out how to dispatch commands.
What I don't understand though is why there are two kinds of things at all. If you want to execute a command, then why not simply put the execution logic on the command object?
There are also other design patterns you could use here other than this clunky dictionary lookup based on types. For example:
a command handler could have a method that takes a command and returns a boolean, whether the handler can handle this command or not. Now you have a list of command handlers, a command comes in, and you just run down the list asking "are you my handler?" until you find one. If O(n) lookup is too slow, then build a MRU cache or memoize the result or some such thing, and the amortized behaviour will improve.
the dispatch logic could be put into the command handler itself. A command handler is given a command; it either executes it, or it recurses, calling its parent command handler. You can thus build a graph of command handlers that defer work to each other as necessary. (This is basically how QueryService works in COM.)
Upvotes: 8
Reputation: 6813
The problem here is that your definition of Handler<TCommand>
requires TCommand
to be both covariant and contravariant - and that's not allowed.
To pass a Handler<Increment>
into the constructor of CommandBus
(which expects a Handler<Command>
), you must declare Command
as a covariant type parameter in Handler
, like this:
public interface Handler<out TCommand> where TCommand : Command
Making this change allows you to pass in a Handler<AnythingThatImplementsCommand>
wherever a Handler<Command>
is requested, so your constructor for CommandBus
works now.
But this introduces a new issue for the following line:
void Handle(TCommand command);
Since TCommand
is covariant, it is possible to assign a Handler<Increment>
to a Handler<Command>
reference. Then you would be able to call the Handle
method but pass in anything that implements Command
- clearly that's not going to work. To make this call correct, you have to allow TCommand
to be contravariant instead.
Since you can't do both, you'll have to make a concession somewhere. One way to do this is by making using covariance in Handler<TCommand>
, but force an explicit cast in your Handle
method, like this:
public interface Handler<out TCommand> where TCommand : Command
{
Type HandledCommand { get; }
void Handle(Command command);
}
public class IncrementHandler : Handler<Increment>
{
public void Handle(Command command)
{
Counter += ((Increment)command).Value;
}
}
It doesn't prevent somebody from creating an IncrementHandler
and then passing in the wrong kind of Command
, but if the handlers are only used by CommandBus
you can check the type in CommandBus.Dispatch
and have something resembling type safety.
Upvotes: 0
Reputation: 8726
The problem here is that Increment
implements Command
(which I renamed to ICommand
to make that clearer, in the code below).
So it is no longer accepted as a Handler<Command>
, which is what the constructor expects (subtype instead of required supertype, as @Lee pointed out in comments).
If you can generalize to use just ICommand
, it would work:
public class CommandBusTest
{
public void DispatchesProperly()
{
var handler = new IncrementHandler(counter: 0);
var bus = new CommandBus((IHandler<ICommand>)handler);
bus.Dispatch(new Increment(5));
}
}
public class CommandBus
{
private readonly Dictionary<Type, IHandler<ICommand>> handlers;
public CommandBus(params IHandler<ICommand>[] handlers)
{
this.handlers = handlers.ToDictionary(
h => h.HandledCommand,
h => h);
}
public void Dispatch(ICommand commande) { /*...*/ }
}
public interface ICommand { int Value { get; } }
public interface IHandler<TCommand> where TCommand : ICommand
{
Type HandledCommand { get; }
void Handle(TCommand command);
}
public class Increment : ICommand
{
public Increment(int value) { Value = value; }
public int Value { get; }
}
public class IncrementHandler : IHandler<ICommand>
{
// Handler<ICommand>
public Type HandledCommand => typeof(Increment);
public void Handle(ICommand command)
{
Counter += command.Value;
}
// Handler<ICommand>
public int Counter { get; private set; }
public IncrementHandler(int counter)
{
Counter = counter;
}
}
Upvotes: 0