5

I'm trying to develop a generic command processor. I would like to create command handler classes implementing a given interface. I'll use inversion of control to dinamically create an instance of the appropriate class based on the type of command received. Then I'd like to call the "Execute" method of the class in a generic way.

I'm able to make this work using a covariant type parameter but in this case I can't use the generic type parameter as a method parameter.

It would seem that a contravariant approach should work, because it allows me to declare the method parameters as desired, but unfortunately the instance of the class can't be converted to the base interface.

The code below exemplifies the problem:

using System;
using System.Diagnostics;

namespace ConsoleApplication2
{
    // Command classes

    public class CommandMessage
    {
        public DateTime IssuedAt { get; set; }
    }

    public class CreateOrderMessage : CommandMessage
    {
        public string CustomerName { get; set; }
    }

    // Covariant solution

    public interface ICommandMessageHandler1<out T> where T : CommandMessage
    {
        void Execute(CommandMessage command);
    }

    public class CreateOrderHandler1 : ICommandMessageHandler1<CreateOrderMessage>
    {
        public void Execute(CommandMessage command)
        {
            // An explicit typecast is required
            var createOrderMessage = (CreateOrderMessage) command;
            Debug.WriteLine("CustomerName: " + createOrderMessage.CustomerName);
        }
    }

    // Contravariant attempt (doesn't work)

    public interface ICommandMessageHandler2<in T> where T : CommandMessage
    {
        void Execute(T command);
    }

    public class CreateOrderHandler2 : ICommandMessageHandler2<CreateOrderMessage>
    {
        public void Execute(CreateOrderMessage command)
        {
            // Ideally, no typecast would be required
            Debug.WriteLine("CustomerName: " + command.CustomerName);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var message = new CreateOrderMessage {CustomerName = "ACME"};

            // This code works
            var handler1 = new CreateOrderHandler1();
            ICommandMessageHandler1<CreateOrderMessage> handler1b = handler1;
            var handler1c = (ICommandMessageHandler1<CommandMessage>) handler1;
            handler1c.Execute(message);

            // This code throws InvalidCastException
            var handler2 = new CreateOrderHandler2();
            ICommandMessageHandler2<CreateOrderMessage> handler2b = handler2;
            var handler2c = (ICommandMessageHandler2<CommandMessage>)handler2;  // throws InvalidCastException
            handler2c.Execute(message);
        }
    }
}
Fernando Correia
  • 21,803
  • 13
  • 83
  • 116

3 Answers3

2

You can cast generic interfaces with out generic parameters only to interfaces with more specific parameters. E.g. ICommandMessageHandler1<CommandMessage> could be casted to ICommandMessageHandler2<CreateOrderMessage> (Execute(CommandMessage command) will also accept CreateOrderMessage), but not vice versa.

Try to think, for example, if the cast throwing an InvalidCastException in your code would be allowed, what would happened if you called handler2c.Execute(new CommandMessage())?

penartur
  • 9,792
  • 5
  • 39
  • 50
  • I agree. Your explanation is clear. If I could do that typecast, I would be able to call Execute on an instance of CreateOrderHandler2 with a parameter of type CommandMessage when it expects a CreateOrderMessage. That approach doesn't really seem to make sense. The requiredment for an explicit typecast inside Execute when using the covariant solution doesn't seem so unwieldy anymore. – Fernando Correia Feb 15 '12 at 18:22
0

The interfaces ICommandMessageHandler1<T> and ICommandMessageHandler2<T> are not related to each other. Just because both have a method Execute does not make them compatible. This would be duck-typing, which is not supported in c#.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • My example was wrong. I'm not trying to do any conversion between ICommandMessageHandler1 and ICommandMessageHandler2; they're just two different and separate alternatives I've tried. The second-to-last line should read: "var handler2c = (ICommandMessageHandler2)handler2;" – Fernando Correia Feb 15 '12 at 18:13
0

Perhaps I don't really get what you want to do, but this works fine for me without any typecasts.

public class CommandMessage
{
    public DateTime IssuedAt
    {
        get;
        set;
    }
}

public class CreateOrderMessage : CommandMessage
{
    public string CustomerName
    {
        get;
        set;
    }
}

public interface ICommandMessageHandler2<in T> where T : CommandMessage
{
    void Execute(T command);
}
public class CreateOrderHandler2 : ICommandMessageHandler2<CreateOrderMessage>
{
    public void Execute(CreateOrderMessage command)
    {
        // No typecast is required
        Debug.WriteLine("CustomerName: " + command.CustomerName);
    }
}

class Program
{
    static void Main(string[] args)
    {
        var message = new CreateOrderMessage
        {
            CustomerName = "ACME"
        };

        // This code throws InvalidCastException
        var handler2 = (ICommandMessageHandler2<CreateOrderMessage>)new CreateOrderHandler2();
        handler2.Execute(message);
    }
}
Ani
  • 10,826
  • 3
  • 27
  • 46
  • That does work, but only for the case when I have only one kind of command, because there is an explicit typecast using . What I'm trying to do is to have a generic command processor that can receive different command messages and dispatch them to an instance of the appropriate handler class. I could probably hack that using reflection, but I'm trying to use inversion of control to create an instance of the appropriate class (that part I have already solved) and then call the Execute method without specifying a derived class. The "Covariant solution" in my example works. – Fernando Correia Feb 15 '12 at 18:27