在C#中双重发送?

Ode*_*ded 62 c# language-features design-patterns double-dispatch

我听过/读过这个词,但不太明白这意味着什么.

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

Mar*_*ade 55

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

当您希望根据运行时的类型而不是编译时选择给定参数使用哪种方法时,它非常有用.

双重调度是多次调度的特例.

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

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

在多分派中,一个方法可以传递多个参数,并且使用哪个实现取决于每个参数的类型.评估类型的顺序取决于语言.在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).
    }
}       
Run Code Online (Sandbox Code Playgroud)

  • 我不太确定这是个好主意.这根本没有真正实现双重调度,我需要知道*在compile -t*是什么类型的东西是为了指定类型参数!! 你也可以不用所有的反射代码来简单地抛出对象.我错过了什么吗? (26认同)
  • 它确实实现了双重调度,因为它调度对象的运行时类型(派生自DoubleDispatch)和方法参数.返回类型的反射用于将机制扩展到子类,因此您可以将"string Foo(string)"添加到子类中,它将起作用. (4认同)
  • LINQ与反射的精彩使用 - 我之前没想过将它应用于那些繁琐的xxxInfo对象.谢谢! (2认同)
  • 鉴于我们现在在C#中有一个_dynamic_类型,这只是出于历史目的.随意编辑我的答案. (2认同)
  • 如果使用反射来解释双重调度,我认为这个例子不是那么好。因为我认为反思是不需要的。我认为一个例子应该能展示本质。可能我只是没有看到双重调度正在解决的问题。 (2认同)

Zen*_*ker 11

好吧,嘿伙计们,马克发布的代码并不完整,什么都没有用.

所以调整和完成.

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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


Mik*_*keJ 10

其他答案使用泛型和运行时类型系统。但需要明确的是,泛型和运行时类型系统的使用与双重分派没有任何关系。它们可以用来实现它,但双重调度仅依赖于在运行时使用具体类型来调度调用。我认为维基百科页面上的说明更清楚。我将在下面包含翻译后的 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]);
    }
}
Run Code Online (Sandbox Code Playgroud)


Mic*_*ann 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"
}
Run Code Online (Sandbox Code Playgroud)

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