65

我听过/读过这个词,但不太明白它的意思。

我什么时候应该使用这种技术,我将如何使用它?谁能提供一个好的代码示例?

4

5 回答 5

58

访问者模式是一种以面向对象的方式进行双重调度的方式。

当您想根据运行时的类型而不是编译时的类型为给定参数选择使用哪种方法时,它很有用。

双重分派是多重分派的特例。

当您在对象上调用虚拟方法时,这被认为是单调度,因为调用哪个实际方法取决于单个对象的类型。

对于双重分派,对象的类型和方法唯一参数的类型都被考虑在内。这类似于方法重载解析,只是参数类型是在运行时以双调度而不是在编译时静态确定的。

在多分派中,一个方法可以有多个参数传递给它,并且使用哪个实现取决于每个参数的类型。评估类型的顺序取决于语言。在 LISP 中,它从头到尾检查每种类型。

具有多个分派的语言使用泛型函数,它们只是函数声明,不像泛型方法,后者使用类型参数。

要在 C# 中进行双重调度,您可以声明一个具有唯一对象参数的方法,然后声明具有特定类型的特定方法:

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).
    }
}       
于 2008-09-03T21:38:54.177 回答
11

Mark 发布的代码不完整,并且曾经存在的代码不起作用。

如此调整和完整。

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

感谢 Mark 和其他人对 Double Dispatcher 模式的精彩解释。

于 2011-06-08T04:38:55.650 回答
6

C# 4 引入了dynamic在运行时(而不是编译时)解析函数调用的伪类型。(即使用表达式的运行时类型)。双重(或多重调度)可以简化为:

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"
}

还要注意,通过使用dynamic您可以防止编译器的静态分析器检查这部分代码。因此,您应该仔细考虑使用dynamic.

于 2017-07-12T09:29:02.140 回答
3

其他答案使用泛型和运行时类型系统。但要明确的是,泛型和运行时类型系统的使用与双重分派没有任何关系。它们可用于实现它,但双重调度仅依赖于在运行时使用具体类型来调度调用。我认为在wikipedia page中说明得更清楚。我将在下面包含翻译后的 C++ 代码。关键是 SpaceShip 上的虚拟 CollideWith,它在 ApolloSpacecraft 上被覆盖。这是“双重”调度发生的地方,并且为给定的宇宙飞船类型调用了正确的小行星方法。

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]);
    }
}
于 2020-05-18T19:54:07.287 回答
0

工作代码的完整列表

using System;
using System.Linq;

namespace TestConsoleApp
{
    internal class Program
    {
        public static void Main(string[] args)
        {
            const int x = 5;
            var dispatch = new DoubleDispatch();

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

            Console.ReadLine();
        }
    }

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

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

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

        public string Foo(string arg)
        {
            return arg;
        }
    }
}
于 2019-12-17T08:41:51.233 回答