68 votes

Double dispatch en C# ?

J'ai entendu/lu ce terme mais je ne comprends pas bien ce qu'il signifie.

Quand dois-je utiliser cette technique et comment dois-je l'utiliser ? Quelqu'un peut-il me fournir un bon exemple de code ?

0 votes

Actuellement, c'est la meilleure façon de procéder : blogs.msdn.microsoft.com/curth/2008/11/15/

0 votes

0 votes

Attention à la double expédition . Vous pouvez probablement l'éviter pour une meilleure maintenance du code.

59voto

Mark Cidade Points 53945

Le modèle visiteur est une façon de faire du double-dispatch d'une manière orientée objet.

Elle est utile lorsque vous souhaitez choisir la méthode à utiliser pour un argument donné en fonction de son type au moment de l'exécution plutôt qu'au moment de la compilation.

La double répartition est un cas particulier de envoi multiple .

Lorsque vous appelez une méthode virtuelle sur un objet, on considère qu'il s'agit d'une méthode à distribution unique car la méthode réelle appelée dépend du type de l'objet unique.

Pour le double dispatch, le type de l'objet et le type de l'argument unique de la méthode sont tous deux pris en compte. C'est comme la résolution de surcharge de méthode, sauf que le type de l'argument est déterminé au moment de l'exécution dans la double répartition au lieu d'être déterminé statiquement au moment de la compilation.

Dans le cas de la répartition multiple, plusieurs arguments peuvent être transmis à une méthode et l'implémentation utilisée dépend du type de chaque argument. L'ordre dans lequel les types sont évalués dépend du langage. En LISP, il vérifie chaque type du premier au dernier.

Les langages avec répartition multiple utilisent des fonctions génériques, qui ne sont que des délcarations de fonctions et ne sont pas comme les méthodes génériques, qui utilisent des paramètres de type.

Pour faire du double-dispatch en C# Vous pouvez déclarer une méthode avec un seul argument objet, puis des méthodes spécifiques avec des types spécifiques :

using System.Linq;  

class DoubleDispatch
{ 
    public T Foo<T>(object arg)
    { 
        var method = from m in GetType().GetMethods()
                   where    m.Name == "Foo" 
                         && m.GetParameters().Length==1
                         && arg.GetType().IsAssignableFrom
                                           (m.GetParameters()[0].GetType())
                         && m.ReturnType == typeof(T)
                   select m;

        return (T) method.Single().Invoke(this,new object[]{arg});          
    }

    public int Foo(int arg) { /* ... */ }

    static void Test() 
    { 
        object x = 5;
        Foo<int>(x); //should call Foo(int) via Foo<T>(object).
    }
}

3 votes

Merveilleuse utilisation de LINQ avec réflexion - je n'avais pas pensé à l'appliquer à ces fastidieux objets xxxInfo auparavant. Merci !

29 votes

Je ne suis pas sûr que ce soit une bonne idée. Cela n'implémente pas vraiment le double dispatch, j'ai besoin de connaître à compile-t quel est le type de la chose afin de spécifier le paramètre de type ! !! Vous pouvez tout aussi bien ne pas vous embêter avec tout le code de réflexion et simplement couler l'objet. Est-ce que quelque chose m'échappe ?

4 votes

Il implémente la double répartition en ce sens qu'il répartit à la fois le type d'exécution de l'objet (dérivé de DoubleDispatch) et l'argument de la méthode. La réflexion sur le type de retour est utilisée pour étendre le mécanisme aux sous-classes, de sorte que vous pouvez ajouter "string Foo(string)" à une sous-classe et cela fonctionnera.

12voto

zenwalker Points 1433

Le code posté par Mark n'est pas complet et ce qu'il contient ne fonctionne pas.

Donc, ajusté et complet.

class DoubleDispatch
{
    public T Foo<T>(object arg)
    {
        var method = from m in GetType().GetMethods(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic)
                     where m.Name == "Foo"
                           && m.GetParameters().Length == 1
                           //&& arg.GetType().IsAssignableFrom
                           //                  (m.GetParameters()[0].GetType())
                           &&Type.GetType(m.GetParameters()[0].ParameterType.FullName).IsAssignableFrom(arg.GetType())
                           && m.ReturnType == typeof(T)
                     select m;

        return (T)method.Single().Invoke(this, new object[] { arg });
    }

    public int Foo(int arg)
    {
        return 10;
    }

    public string Foo(string arg)
    {
        return 5.ToString();
    }

    public static void Main(string[] args)
    {
        object x = 5;
        DoubleDispatch dispatch = new DoubleDispatch();

        Console.WriteLine(dispatch.Foo<int>(x));

        Console.WriteLine(dispatch.Foo<string>(x.ToString()));

        Console.ReadLine();
    }
}

Merci Mark et aux autres pour les explications sur le motif Double Dispatcher.

0 votes

Je suis d'accord. Ce code est complet et montre ce qu'est vraiment le double dispatch. L'explication de la réponse de Mark et ce code vont parfaitement ensemble.

0 votes

N'écrirait-on pas "Console.WriteLine(dispatch.Foo<x.GetType()>(x)) ;", ce qui serait une façon plus dynamique et plus utile de tirer parti du modèle ?

7voto

Micha Wiedenmann Points 3357

C# 4 introduit le pseudo type dynamic qui résout l'appel de fonction au moment de l'exécution (au lieu du moment de la compilation). (C'est-à-dire que le type d'exécution de l'expression est utilisé). La double (ou multi) répartition peut être simplifiée comme suit :

class C { }

static void Foo(C x) => Console.WriteLine(nameof(Foo));
static void Foo(object x) => Console.WriteLine(nameof(Object));

public static void Main(string[] args)
{
    object x = new C();

    Foo((dynamic)x); // prints: "Foo"
    Foo(x);          // prints: "Object"
}

Notez également qu'en utilisant dynamic vous empêchez l'analyseur statique du compilateur d'examiner cette partie du code. Vous devez donc considérer avec attention l'utilisation de dynamic .

6voto

MikeJ Points 79

Les autres réponses utilisent les génériques et le système de type d'exécution. Mais pour être clair, l'utilisation des génériques et du système de types d'exécution n'a rien à voir avec la double répartition. Ils peuvent être utilisés pour l'implémenter mais la double répartition dépend simplement de l'utilisation du type concret au moment de l'exécution pour répartir les appels. Je pense que cela est illustré plus clairement dans la page wikipedia . Je vais inclure le code C++ traduit ci-dessous. La clé de tout ceci est le CollideWith virtuel sur SpaceShip et le fait qu'il soit surchargé sur ApolloSpacecraft. C'est là que le "double" dispatch a lieu et que la méthode astéroïde correcte est appelée pour le type de vaisseau spatial donné.

class SpaceShip
{
    public virtual void CollideWith(Asteroid asteroid)
    {
        asteroid.CollideWith(this);
    }
}

class ApolloSpacecraft : SpaceShip
{
    public override void CollideWith(Asteroid asteroid)
    {
        asteroid.CollideWith(this);
    }
}

class Asteroid
{
    public virtual void CollideWith(SpaceShip target)
    {
        Console.WriteLine("Asteroid hit a SpaceShip");
    }

    public virtual void CollideWith(ApolloSpacecraft target)
    {
        Console.WriteLine("Asteroid hit ApolloSpacecraft");
    }
}

class ExplodingAsteroid : Asteroid
{
    public override void CollideWith(SpaceShip target)
    {
        Console.WriteLine("ExplodingAsteroid hit a SpaceShip");
    }

    public override void CollideWith(ApolloSpacecraft target)
    {
        Console.WriteLine("ExplodingAsteroid hit ApolloSpacecraft");
    }
}

class Program
{
    static void Main(string[] args)
    {
        Asteroid[] asteroids = new Asteroid[] { new Asteroid(), new ExplodingAsteroid() };

        ApolloSpacecraft spacecraft = new ApolloSpacecraft();

        spacecraft.CollideWith(asteroids[0]);
        spacecraft.CollideWith(asteroids[1]);

        SpaceShip spaceShip = new SpaceShip();

        spaceShip.CollideWith(asteroids[0]);
        spaceShip.CollideWith(asteroids[1]);
    }
}

1voto

Brad Wilson Points 22910

La double-dispatch est un autre nom pour le Modèle de visiteur .

J'ai écrit un article il y a quelques années sur l'utilisation de Reflection pour mettre en œuvre le modèle Visitor. http://www.agileprogrammer.com/dotnetguy/articles/ReflectionVisitor.aspx

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