如何使用逆变参数将通用接口转换为基类型?

时间:2012-02-15 17:38:00

标签: c# generics covariance contravariance

我正在尝试开发通用命令处理器。我想创建实现给定接口的命令处理程序类。我将使用控制反转来根据接收到的命令类型动态创建相应类的实例。然后我想以通用的方式调用类的“Execute”方法。

我能够使用协变类型参数来完成这项工作,但在这种情况下,我不能使用泛型类型参数作为方法参数。

似乎逆变方法应该有效,因为它允许我根据需要声明方法参数,但不幸的是,类的实例无法转换为基接口。

以下代码举例说明了问题:

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

3 个答案:

答案 0 :(得分:2)

您只能将具有out通用参数的通用接口转换为具有更多特定参数的接口。例如。 ICommandMessageHandler1<CommandMessage>可以投放到ICommandMessageHandler2<CreateOrderMessage>Execute(CommandMessage command)也会接受CreateOrderMessage),但反之亦然。

尝试考虑,例如,是否允许在您的代码中投射InvalidCastException的投射,如果您调用handler2c.Execute(new CommandMessage())会发生什么?

答案 1 :(得分:0)

接口ICommandMessageHandler1<T>ICommandMessageHandler2<T>彼此无关。只是因为两者都有方法Execute不能使它们兼容。这将是鸭子打字,c#不支持。

答案 2 :(得分:0)

也许我真的没有得到你想要做的事情,但这对我来说没有任何类型转换。

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