Counter-control? Covariance? What is wrong with this common architecture ...?

I'm having trouble setting up the command processing architecture. I want to be able to create several different commands derived from ICommand; then create several different command handlers received from ICommandHandler;

Here's the interface and classes that I started defining:

interface ICommand {} class CreateItemCommand : ICommand {} interface ICommandHandler<TCommand> where TCommand : ICommand { void Handle(TCommand command); } class CreateItemCommandHandler : ICommandHandler<CreateItemCommand> { public void Handle(CreateItemCommand command) { // Handle the command here } } 

I have a helper class that can create the appropriate type of command:

 class CommandResolver { ICommand GetCommand(Message message) { return new CreateItemCommand(); // Handle other commands here } } 

And, a helper class that creates the appropriate handler; this is where i have problems:

 class CommandHandlerResolver { public ICommandHandler<TCommand> GetHandler<TCommand>(TCommand command) { // I'm using Ninject and have an instance of an IKernel // The following code throws an exception despite having a proper binding // _kernel.GetService(typeof(ICommandHandler<TCommand>)) var bindingType = typeof(ICommandHandler<>).MakeGenericType(command.GetType()); var handler = _kernel.GetService(bindingType); return handler as ICommandHandler<TCommand>; // handler will be null after the cast } } 

Here is the main running method

 CommandResolver _commandResolver; HandlerResolver _handlerResolver; void Run() { // message is taken from a queue of messages var command = _commandResolver.GetCommand(message); var handler = _handlerResolver.GetHandler(command); // handler will always be null handler.Handle(command); } 

I might think of several different ways to refactor the code, which I'm sure will avoid this problem, but I was a bit puzzled by the problem and wanted to understand more about what was going on.

This design looks as if it should work.

+7
generics c # covariance architecture contravariance
source share
1 answer

Problem

Your problem is that you mix static types and runtime types: you write code that relies on the creation of generic types, but then you invoke it with the basic types of interfaces.

Track through your main thread:

Your CommandResolver always returns a static ICommand type. When you speak:

 var command = _commandResolver.GetCommand(message); var handler = _handlerResolver.GetHandler(command); 

The command type is bound to ICommand and then passed to GetHander , which calls GetHandler<ICommand> . That is, TCommand in this call is always bound to ICommand .

This is the main problem here . Because TCommand always ICommand , doing:

 _kernel.GetService(typeof(ICommandHandler<TCommand>)) 

... does not work (it searches for ICommandHandler<ICommand> , but the kernel does not have it); and even if it worked, you would need to return it as ICommandHandler<ICommand> , since this is the return type of the method.

By calling GetHandler , not knowing (at compile time) the actual type of command, you have lost the ability to use generics effectively, and TCommand becomes meaningless.

So, you are trying to get around this: your resolver uses the command execution type ( command.GetType() ) to reflect the construct like ICommandHandler<SomeCommandType> and tries to find it in the kernel.

Assuming you have something registered for this type, you get ICommandHandler<SomeCommandType> , which then tries to apply to ICommandHandler<ICommand> (remember that TCommand bound to ICommand ). This, of course, will not work unless TCommand is declared covariant in ICommandHandler<TCommand> , since you are dropping the type hierarchy; but even if that happened, that’s not what you want, because what would you do with ICommandHandler<ICommand> anyway?

Simply put: you cannot use ICommandHandler<SomeCommand> for ICommandHandler<ICommand> because it means that you can pass any type of ICommand , and it will be happy to handle it - which is wrong. If you want to use type type parameters, you will need to bind them to the type of the real command in the entire stream.

Decision

One solution to this problem is to keep TCommand bound to the real type of command in the entire resolution of both the command and the command handler, for example. having something like FindHandlerAndHandle<TCommand>(TCommand command) and calling it reflection using the runtime type. But it is smelly and clumsy, and for good reason: you are abusing generics.

General type parameters are intended to help you when you know, at compile time, the type you want, or what you can combine with another type parameter. In such cases, when you do not know this type of runtime, an attempt to use generic tools only bothers you.

A cleaner way to solve this problem is to split the context when you know the type of command (when you write a handler for it) from the context, when you don't know it (when you try to generally find a handler for a general command). A good way to do this is to use the "untyped interface, typed base class" template:

 public interface ICommandHandler // Look ma, no typeparams! { bool CanHandle(ICommand command); void Handle(ICommand command); } public abstract class CommandHandlerBase<TCommand> : ICommandHandler where TCommand : ICommand { public bool CanHandle(ICommand command) { return command is TCommand; } public void Handle(ICommand command) { var typedCommand = command as TCommand; if (typedCommand == null) throw new InvalidCommandTypeException(command); Handle(typedCommand); } protected abstract void Handle(TCommand typedCommand); } 

This is a common way to overcome common and non-common worlds: you use non-common interfaces when calling them, but when implementing, use a base base class. Your main thread now looks like this:

 public void Handle(ICommand command) { var allHandlers = Kernel.ResolveAll<ICommandHandler>(); // you can make this a dependency var handler = allHandlers.FirstOrDefault(h => h.CanHandle(command)); if (handler == null) throw new MissingHandlerException(command); handler.Handle(command); } 

It is also somewhat more stable in the sense that the actual type of command execution should not coincide one after another with the type of handler, so if you have ICommandHandler<SomeBaseCommandType> , it can process commands like SomeDerivedCommandType , so you can create handlers of intermediate base classes in the hierarchy of command types or use other inheritance tricks.

+7
source share

All Articles