5 votes

Comment couler des interfaces génériques utilisant des paramètres contravariants vers un type de base ?

J'essaie de développer un processeur de commande générique. Je voudrais créer des classes de gestionnaires de commandes implémentant une interface donnée. J'utiliserai l'inversion de contrôle pour créer de manière dynamique une instance de la classe appropriée en fonction du type de commande reçue. J'aimerais ensuite appeler la méthode "Execute" de la classe de manière générique.

Je suis capable de faire fonctionner cela en utilisant un paramètre de type covariant mais dans ce cas, je ne peux pas utiliser le paramètre de type générique comme paramètre de méthode.

Il semblerait qu'une approche contravariante devrait fonctionner, car elle me permet de déclarer les paramètres de la méthode comme je le souhaite, mais malheureusement l'instance de la classe ne peut pas être convertie en interface de base.

Le code ci-dessous illustre le problème :

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

2voto

penartur Points 5450

Vous pouvez utiliser des interfaces génériques avec out paramètres génériques uniquement aux interfaces avec plus spécifique paramètres. Par exemple ICommandMessageHandler1<CommandMessage> pourrait être jeté à ICommandMessageHandler2<CreateOrderMessage> ( Execute(CommandMessage command) acceptera également CreateOrderMessage ), mais pas l'inverse.

Essayez de penser, par exemple, si le lancer d'une InvalidCastException dans votre code serait autorisé, que se passerait-il si vous appeliez handler2c.Execute(new CommandMessage()) ?

0voto

Les interfaces ICommandMessageHandler1<T> y ICommandMessageHandler2<T> ne sont pas liés entre eux. Ce n'est pas parce que les deux ont une méthode Execute ne les rend pas compatibles. Il s'agirait d'un typage en canard, qui n'est pas supporté en C#.

0voto

ananthonline Points 7431

Peut-être que je ne comprends pas vraiment ce que vous voulez faire, mais cela fonctionne très bien pour moi, sans aucun typage.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X