Interface covariance convariance: why doesn't it compile?
I want to implement CommandBus
which can Dispatch
from Commands
to CommandHandlers
.
- A
Command
is a simple DTO describing what should happen. For example: "Increment counter by 5" - A
CommandHandler
is capable of handling the exact typeCommand
. -
CommandBus
acceptsCommand
and executesCommandHandler
who is capable of handling it.
The code I have written does not compile.
The compiler is complaining cannot convert from 'IncrementHandler' to 'Handler<Command>'
. I don't understand why, because it IncrementHandler
implements Handler<Increment>
and Increment
implementsCommand
I tried modifiers in
and out
on common interfaces, it does not solve the problem.
Is there a way to achieve this with just 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;
}
}
source to share
I don't understand why, because it
IncrementHandler
implementsHandler<Increment>
andIncrement
implementsCommand
Fix your misunderstanding and then the rest will become clear.
Suppose what you wanted to do was legal. What's 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 it Handler<Command>.Handle
takes on a value Command
, and <29> is Command
.
So what happened? You just passed the decrement command in ih
via h
, but ih
is that IncrementHandler
that only knows how to handle increments.
Since this is pointless, something must be illegal here; which line would you like to be illegal? The C # team decided that the conversion is something that should be illegal.
More specific:
Your program uses reflection in an attempt to complete the type system safety check, and then you complain that the type system stops you when you write something unsafe . Why are you using generics at all?
Generics (partially) enforce type safety and then you send a reflection based dispatch. It doesn't make any sense; don't take steps to improve type safety and then make heroic efforts to get around them.
Generally you want to work on type safety, so don't use generics at all. Just create an interface ICommand
and a class Handler
that will take a command and then find some mechanism to develop ways to send commands.
However, I don't understand why there are two types of things. If you want to execute a command, why not just put the execution logic in the command object?
There are other design patterns you could use here besides this awkward type-based dictionary search. For example:
-
a command handler can have a method that accepts a command and returns a boolean whether the handler can process that command or not. Now you have a list of command handlers, the command comes in, and you just start the list by asking, "Are you my handler?" until you find it. If O (n) lookup is too slow then create an MRU cache or memoize the result or something like that and the improved behavior will improve.
-
the dispatch logic can be placed in the command handler itself. The command handler receives the command; it either executes it or it recurses by calling its parent command handler. This way, you can build a graph of command handlers that postpone work with each other as needed. (This basically works like a QueryService in COM.)
source to share
The problem is what Increment
implements Command
(which I renamed to ICommand
to make it clearer, in the code below). Thus, it is no longer accepted as Handler<Command>
what the constructor expects (subtype instead of the required supertype as @Lee pointed out in the comments).
If you can only generalize ICommand
, this will 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;
}
}
source to share
The problem here is that your definition Handler<TCommand>
requires it to TCommand
be both covariant and contravariant - and that's not allowed.
To pass Handler<Increment>
to a constructor CommandBus
(which expects a Handler<Command>
), you must declare Command
as a parameter of the covariance type in Handler
, for example:
public interface Handler<out TCommand> where TCommand : Command
Making this change allows you to pass Handler<AnythingThatImplementsCommand>
wherever requested Handler<Command>
, so your constructor for CommandBus
works now.
But this introduces a new problem for the next line:
void Handle(TCommand command);
Since it TCommand
is covariant, you can assign a to a Handler<Increment>
link Handler<Command>
. You will then be able to call the method Handle
, but pass everything that implements Command
, which obviously won't work. To make this call correct, you must allow TCommand
contravariants instead .
Since you cannot do both, you will need a concession somewhere. One way to do this is to do covariance in Handler<TCommand>
, but force an explicit conversion in your method Handle
, 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;
}
}
This doesn't stop someone from creating IncrementHandler
and then passing in the wrong type Command
, but if handlers are only used CommandBus
, you can check the type in CommandBus.Dispatch
and have something resembling type safety.
source to share