Question

I would like to maintain a list of delegates (here: "mCommandHandlers"). Since they're generic delegates, I actually defined a second type of delegates such that I can maintain such a list:

public delegate void CommandHandler<TCommand>(TCommand command) where TCommand : ICommand;
public delegate void ICommandHandler(ICommand command);
Dictionary<Type, ICommandHandler> mCommandHandlers;

I would use the first type for compile-time advantages, such as knowing exactly what kind of TCommand is being used in the implementation of my delegate:

RegisterHandler<ResourceCommand>((command) =>
{
       if (command != null)
       {
            ResourceManager.ResourceReceived(command.ResourceName, command.ResourceHash, command.ResourceData);
       }
});

Inside RegisterHandler, I would now like to do the following:

public void RegisterHandler<TCommand>(CommandHandler<TCommand> handler) where TCommand : ICommand
{
      mCommandHandlers.Add(typeof(TCommand), handler);
}

But I get the following error message:

Error 3 Argument 2: cannot convert from CommandHandler<TCommand>' to 'ICommandHandler'

Why is this? Should the compiler see that in fact my first delegate type demands the argument to be at least of type ICommand, assuring that the delegate instance conforms to the signature of the second delegate type as well?

Was it helpful?

Solution 2

Should the compiler see that in fact my first delegate type demands the argument to be at least of type ICommand, assuring that the delegate instance conforms to the signature of the second delegate type as well?

There are two problems here.

Firstly, delegate variance doesn't allow for an implicit reference conversion of one delegate type to another - it allows you to create a new delegate instance from a compatible existing one.

Secondly, you've got the variance the wrong way round CommandHandler<TCommand> will only accept a specific type of command... whereas ICommandHandler will accept any ICommand.

So suppose we could do this:

CommandHandler<FooCommand> fooHandler = HandleFoo;
ICommandHandler generalHandler = new ICommandHandler(fooHandler);

Then we could call:

generalHandler(new BarCommand());

... how would you expect the HandleFoo method to cope with that?

There is a conversion from ICommandHandler to CommandHandler<TCommand> for any particular TCommand, because when the new delegate is called, that will always be valid. Sample code:

using System;

delegate void CommandHandler<TCommand>(TCommand command)
    where TCommand : ICommand;
delegate void ICommandHandler(ICommand command);

interface ICommand {}

class Command : ICommand {}

class Test
{
    public static void Main()
    {
        ICommandHandler x = null;
        CommandHandler<Command> y = new CommandHandler<Command>(x);
    }
}

I suggest you just change your dictionary to:

Dictionary<Type, Delegate> mCommandHandlers;

Then when you need to invoke any particular delegate, you'll need to cast to the right kind of handler - which I assume you'll know due to a type parameter at that point. Or you could create a proxy handler which performs the cast, as per Jared's answer.

OTHER TIPS

The problem is the two delegate types simply aren't compatible. In order to make this work you are going to need to add an indirection layer which converts the arguments between ICommand and TCommand.

public void RegisterHandler<TCommand>(CommandHandler<TCommand> handler)
  where TCommand : ICommand
{
  mCommandHandlers.Add(
    typeof(TCommand), 
    (command) => handler((TCommand)command);
  );
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top