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

发布于 2025-01-05 10:35:56 字数 2381 浏览 4 评论 0原文

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

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

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

下面的代码举例说明了该问题:

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

明媚如初 2025-01-12 10:35:56

您只能将具有 out 泛型参数的泛型接口强制转换为具有更多特定 参数的接口。例如,ICommandMessageHandler1 可以转换为 ICommandMessageHandler2Execute(CommandMessage command) 也将接受 CreateOrderMessage),但反之则不然。

例如,尝试思考一下,如果允许在代码中抛出 InvalidCastException 的强制转换,那么如果您调用 handler2c.Execute(new CommandMessage()) 会发生什么?

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())?

路还长,别太狂 2025-01-12 10:35:56

接口 ICommandMessageHandler1ICommandMessageHandler2 彼此不相关。仅仅因为两者都有一个方法 Execute 并不能使它们兼容。这将是鸭子类型,c# 中不支持这种类型。

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#.

烟花肆意 2025-01-12 10:35:56

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

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

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);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文