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);
}
}
}
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())
?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With