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 type Command

    .
  • CommandBus

    accepts Command

    and executes CommandHandler

    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;
  }
}

      

+3


source to share


3 answers


I don't understand why, because it IncrementHandler

implements Handler<Increment>

and Increment

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.)

+8


source


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;
    }
}

      

0


source


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.

0


source







All Articles