【问题标题】:How to cast generic interfaces using contravariant parameters to a base type?如何使用逆变参数将泛型接口转换为基本类型?
【发布时间】:2012-02-16 02:15:14
【问题描述】:

我正在尝试开发通用命令处理器。我想创建实现给定接口的命令处理程序类。我将使用控制反转根据收到的命令类型动态地创建适当类的实例。然后我想以通用的方式调用该类的“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);
        }
    }
}

【问题讨论】:

    标签: c# generics covariance contravariance


    【解决方案1】:

    您只能将具有out 泛型参数的泛型接口转换为具有更多特定参数的接口。例如。 ICommandMessageHandler1&lt;CommandMessage&gt; 可以转换为 ICommandMessageHandler2&lt;CreateOrderMessage&gt;Execute(CommandMessage command) 也将接受 CreateOrderMessage),但反之则不行。

    试想一下,例如,如果允许演员在您的代码中抛出 InvalidCastException,如果您调用 handler2c.Execute(new CommandMessage()),会发生什么?

    【讨论】:

    • 我同意。你的解释很清楚。如果我可以进行那种类型转换,那么当它需要一个 CreateOrderMessage 时,我就可以使用 CommandMessage 类型的参数调用 CreateOrderHandler2 实例上的 Execute。这种方法似乎真的没有意义。使用协变解决方案时,在 Execute 中进行显式类型转换的要求似乎不再那么笨拙了。
    【解决方案2】:

    接口ICommandMessageHandler1&lt;T&gt;ICommandMessageHandler2&lt;T&gt; 彼此不相关。仅仅因为两者都有一个方法Execute 并不能使它们兼容。这将是鸭子类型,c# 不支持。

    【讨论】:

    • 我的例子是错误的。我不想在 ICommandMessageHandler1 和 ICommandMessageHandler2 之间进行任何转换;它们只是我尝试过的两种不同且独立的选择。倒数第二行应为:“var handler2c = (ICommandMessageHandler2)handler2;”
    【解决方案3】:

    也许我真的不明白你想要做什么,但这对我来说很好,没有任何类型转换。

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

    【讨论】:

    • 这确实有效,但仅适用于我只有一种命令的情况,因为使用 进行了显式类型转换。我想要做的是拥有一个通用命令处理器,它可以接收不同的命令消息并将它们分派到适当的处理程序类的实例。我可能会使用反射来破解它,但我正在尝试使用控制反转来创建适当类的实例(我已经解决了该部分),然后在不指定派生类的情况下调用 Execute 方法。我的示例中的“协变解决方案”有效。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-29
    相关资源
    最近更新 更多