Как преобразовать универсальные интерфейсы с использованием контравариантных параметров в базовый тип? - PullRequest
4 голосов
/ 15 февраля 2012

Я пытаюсь разработать общий командный процессор. Я хотел бы создать классы обработчиков команд, реализующие данный интерфейс. Я буду использовать инверсию управления для динамического создания экземпляра соответствующего класса в зависимости от типа полученной команды. Затем я хотел бы вызвать метод «Выполнить» класса в общем виде.

Я могу сделать эту работу, используя параметр ковариантного типа, но в этом случае я не могу использовать параметр универсального типа в качестве параметра метода.

Казалось бы, контравариантный подход должен работать, потому что он позволяет мне объявлять параметры метода по желанию, но, к сожалению, экземпляр класса не может быть преобразован в базовый интерфейс.

Код ниже иллюстрирует проблему:

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 ]

2 голосов
/ 15 февраля 2012

Вы можете приводить универсальные интерфейсы с out универсальными параметрами только к интерфейсам с более специфическими параметрами.Например, ICommandMessageHandler1<CommandMessage> можно привести к ICommandMessageHandler2<CreateOrderMessage> (Execute(CommandMessage command) также примет CreateOrderMessage), но не наоборот.

Попробуйте, например, подумать, если бросок бросил InvalidCastException вВаш код был бы разрешен, что случилось бы, если бы вы позвонили handler2c.Execute(new CommandMessage())?

0 голосов
/ 15 февраля 2012

Возможно, я не совсем понимаю, что вы хотите сделать, но это прекрасно работает без каких-либо типов.

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);
    }
}
0 голосов
/ 15 февраля 2012

Интерфейсы ICommandMessageHandler1<T> и ICommandMessageHandler2<T> не связаны друг с другом.Тот факт, что у обоих есть метод Execute, не делает их совместимыми.Это будет типизацией утки, которая не поддерживается в c #.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...