【问题标题】:Double dispatch in C#?C# 中的双重调度?
【发布时间】:2008-09-03 21:03:51
【问题描述】:

我听说过/读过这个词,但不太明白它的含义。

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

【问题讨论】:

标签: c# design-patterns language-features double-dispatch


【解决方案1】:

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

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

双重调度是多重调度的特例。

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

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

在多分派中,一个方法可以有多个参数传递给它,使用哪个实现取决于每个参数的类型。评估类型的顺序取决于语言。在 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).
    }
}       

【讨论】:

  • 通过反射将 LINQ 很好地使用——我以前没想过将它应用到那些乏味的 xxxInfo 对象上。谢谢!
  • 我不太确定这是个好主意。这根本没有真正实现双重调度,我需要知道 at compile-t 事物的类型是什么才能指定类型参数!您最好不要打扰所有反射代码,而只需投射对象。我错过了什么吗?
  • 它确实实现了双重分派,因为它同时在对象的运行时类型(派生自 DoubleDispatch)和方法参数上进行分派。返回类型的反射用于将机制扩展到子类,因此您可以将“string Foo(string)”添加到子类中,它会起作用。
  • 鉴于我们现在在 C# 中有一个 dynamic 类型,这只是出于历史目的。随意编辑我的答案。
  • 如果使用反射来解释双重调度,我认为这个例子并不是那么好。因为我认为不需要反思。我认为一个例子应该显示出本质。可能我只是看不到双重调度正在解决的问题。
【解决方案2】:

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 模式的精彩解释。

【讨论】:

  • 我同意。这段代码是完整的,并显示了双重调度的真正含义。马克答案的解释和这段代码完美地结合在一起。
  • 不会写“Console.WriteLine(dispatch.Foo(x));”是一种更加动态和有用的方式来利用该模式?
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    其他答案使用泛型和运行时类型系统。但要明确的是,泛型和运行时类型系统的使用与双重分派没有任何关系。它们可用于实现它,但双重调度仅依赖于在运行时使用具体类型来调度调用。我认为the 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]);
        }
    }
    

    【讨论】:

      【解决方案5】:

      工作代码的完整列表

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-08-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多