【问题标题】:What is - Single and Multiple Dispatch (in relation to .NET)?什么是 - 单次和多次调度(与 .NET 相关)?
【发布时间】:2010-10-03 14:04:27
【问题描述】:

和重载一样吗,如果不是,能否提供C#中的每一个例子

我已经阅读了对 SO 中提出的类似问题的回复......我不明白发布给它的回复。

here提出了类似的问题

编辑:在 C# 4.0 中使用新的“动态”关键字...这会使语言“多调度”启用吗?

【问题讨论】:

    标签: c# multiple-dispatch single-dispatch


    【解决方案1】:

    多重分派是重载的一种“形式”...

    例如,C# 是单分派,因为 if 仅根据一个参数“this”指针来计算调用什么方法。当你有这样的事情时:

    Base base= new Derived();
    base.DoSomething();
    

    Derived.DoSomething 方法被调用,即使您通过基指针调用它。现在,如果我们有以下内容:

    class Derived : Base
    {
      public override void Process(Stream stream);
      public override void Process(FileStream stream);
      public override void Process(MemoryStream stream);
    }
    

    我们这样做:

    Stream stream= new MemoryStream(...);
    Base b= new Derived();
    b.Process(stream);
    

    然后我们将从Derived 调用Process(Stream) 方法,因为C# 对对象指针(b) 进行一次分派,然后使用编译时间信息来决定调用哪个方法.即使 stream 是 MemoryStream,单个调度系统也会忽略它。

    在多调度系统中,将查看对象指针(如在 C# 中)并检查参数的运行时类型。在上面的例子中,因为 stream 实际上是一个 MemoryStream 系统会调用 Process(MemoryStream) 方法。

    【讨论】:

    • 虽然解释正确,但代码有错误; b.只在Derived中定义的Process不能被调用。
    • 这是一个很好的例子!
    【解决方案2】:

    C# 使用单一分派,其中包括重载方法。当你有代码时

    stringBuilder.Append(parameter);
    

    调度程序查看在 stringBuilder 的类上定义的所有方法,并找到正确的方法。

    对于多分派示例,让我们看一下 Prolog(这是我能想到的第一个)。您可以像这样在 prolog 中定义一个函数:

    func(Arg1, Arg2) :- ....body....
    

    这不是在任何类中定义的,而是在全局范围内定义的。然后,你可以在任意两个参数上调用func(Arg1, Arg2),这个函数就会被调用。如果你想要重载之类的东西,你必须验证函数内部的参数类型,并多次定义它:

    func(Arg1, Arg2) :- is_number(Arg1), is_string(Arg2), ....body....
    func(Arg1, Arg2) :- is_string(Arg1), is_list(Arg2), ....body....
    func(Arg1, Arg2) :- is_number(Arg1), is_list(Arg2), ....body....
    

    然后,您将发送的任何两种参数类型都将被检查 - 这就是多调度部分。

    简而言之,单次分派只查看在第一个参数上定义的方法(在我们的第一个示例中,stringBuilder),然后解析正确的重载以使用其他参数调用。多重分派具有在全局范围内定义的方法/函数,并在重载决策期间将所有参数视为相同。

    我希望我说清楚了,这是一个相当棘手的主题。


    更新:我忘了说,多分派发生在运行时,而单分派发生在编译时。
    更新 #2:显然,这不是真的。

    【讨论】:

    • 我最喜欢的语言之一。希望我得到了正确的谓词,但我已经多年没有使用它了。
    • 单调度和多调度都可以在编译时或运行时完成。动态调度是在运行时绑定代码的术语。
    • @Bill:是的,但是用于多次调度的类型是运行时类型,而用于单次调度的类型(用于参数)是编译时类型。我在这里弄错了吗?
    • 是的,你错了。单个分派可以仅在编译时进行(编译器知道该方法是非虚拟的,因此直接发出函数调用),或者它发出代码以更改调用的函数,具体取决于消息传递到的变量的运行时类型,通常通过 vtable
    【解决方案3】:

    Multi-dispatch 与方法重载有关,但并不相同。前者是动态运行时决策,后者是静态编译时决策。隐含着灵活性的好处,但因此多分派的性能成本。

    AFAIK 一种语言可以支持任何一种,但不能同时支持两者,尽管两者都可以模拟(可以与访问者一起模拟多调度)。 C# 在编译时确定数据类型,因此不是多分派语言,因此无法提供示例。

    (警告:我不是 100% 的)


    附录:实际上 wikipedia 对此有一个 article,这看起来非常详尽,哦,还有一个有用的 LISP 示例

    【讨论】:

    • 在 C# 4.0 中使用新的“动态”关键字...这会使语言“多分派”启用吗?
    • @Nick,不完全是,但应该非常接近。
    • 哈哈 - 我知道我在 Jon Skeet 的领地里很不舒服
    【解决方案4】:

    在面向对象语言中的文本:

    SomeType b;
    a = b.Foo(c, d)
    

    表示对象 b 正在传递一个带有参数 c 和 d 的消息 Foo。 单一分派意味着这个系统只有一个方面负责在运行时确定(可能的许多)方法 Foo 将实际被调用。

    java、c# 和大多数其他 OO 语言使用的模型是,只有运行时类型的“b”才能“决定”实际的方法调用是什么。因此,如果您有 SomeType 的两个实现,它们都提供了不同的 Foo 实现,那么使用哪个类型的决定完全取决于此时恰好是哪种类型。如果 Foo 有多个 重载,那么关于使用哪个重载的决定是编译时决定,仅基于编译时知道 b、c 和 d 的类型。

    这就是单调度,单点选择是与 b 关联的类型系统。

    Multiple Dispatch 将在运行时允许 b、c 和 d 的运行时类型决定调用哪个方法(这样的决定不可避免地会更复杂)。

    在一个更加动态的系统中,定义良好的类型的概念更加流畅(比如一个基于原型的系统,而不是你从 c++/java/C# 中知道的继承模型),然后决定调用什么方法代替完全取决于实际情况 b、c 和 d。

    【讨论】:

      【解决方案5】:

      单/多分派是一种运行时重载。单一调度通常被称为虚函数。调用虚函数时调用的确切函数取决于对象的运行时类型。双分派是一回事,扩展为使用两个对象——通常是this 参数和第二个参数。您可以使用 Vistor 模式轻松实现这一点。多重分派进一步将此概念扩展到更多参数,但在 C# 等语言中实现起来要困难得多(并不是说它不能完成,只是很难)。有些语言开箱即用地实现了此功能。

      例如在 .NET 中,ToString() 函数是单个调度的示例

      // Single dispatch
      Object o = GetSomeObject(); // Return SomeType casted to Object.
      o.ToString(); // Call SomeType::ToString instead of just Object::ToString
      
      // Double dispatch (this version won't work in C#)
      Shape s1 = GetSquare();
      Shape s2 = GetCircle();
      s1.Intersects(s2); // If C# supported double dispatch, this would call Square::Intersects(Circle) not Square::Intersects(Shape)
      

      【讨论】:

        【解决方案6】:
        #include <iostream>
        
        class Pet {
        };
        
        class Cat: public Pet {
        };
        
        class Dog: public Pet {
        };
        
        class Human {
        };
        
        class Man : public Human {
                public:
                        void Kick(Cat& victim);
                        void Kick(Dog& victim);
        };
        
        class Woman : public Human {
                public:
                        void Kick(Cat& victim);
                        void Kick(Dog& victim);
        };
        
        void Man::Kick(Cat& victim) {
                std::cout << "Meow!!!" << std::endl;
        }
        
        void Woman::Kick(Cat& victim) {
                std::cout << "I won't kick a cat" << std::endl;
        }
        
        void Man::Kick(Dog& victim) {
                std::cout << "I won't kick a dog" << std::endl;
        }
        
        void Woman::Kick(Dog& victim) {
                std::cout << "Woof!!!" << std::endl;
        }
        
        int main(int argc, char** argv) {
                Man kicker;
                Dog victim;
                Pet zoo[] = { victim };
                kicker.Kick(victim);
        //      kicker.Kick(zoo[0]);   // No multimethods
                return 0;
        }
        

        就目前而言,C++ 无法在运行时判断 Pet 实际上是 Cat 还是 Dog

        如果有某种方法可以在运行时执行此操作(这样上面的代码将在未注释注释行的情况下编译),则可以说 C++ 支持多调度或多方法。

        【讨论】:

          【解决方案7】:

          在 c# 4.0 中,使用新的动态关键字启用了多方法:

          使用系统; 命名空间示例 { 类轮 { 公共无效RepairWell(){} }

          class Chassis
          {
              public void RepairChassis() { }
          }
          
          class Engine
          {
              public void RepairEngine() { }
          }
          
          class CarWorkshop
          {
              public string Repair(Wheel value)
              {
                  value.RepairWhell();
                  return "wheel repaired";
              }
              public string Repair(Chassis value)
              {
                  value.RepairChassis();
                  return "chassis repaired";
              }
              public string Repair(Engine value)
              {
                  value.RepairEngine();
                  return "engine repaired";
              }
          }
          
          class Program
          {
              static void Main(string[] args)
              {
                  dynamic carWorkshop = new CarWorkshop();
          
                  var whell = new Wheel();
                  var chassis = new Chassis();
                  var engine = new Engine();
          
                  Console.WriteLine(carWorkshop.Repair(whell));
                  Console.WriteLine(carWorkshop.Repair(chassis));
                  Console.WriteLine(carWorkshop.Repair(engine));
                  Console.ReadLine();
              }
          }
          

          }

          在c#中动态引入多方法,一个非常强大的范例。

          【讨论】:

            【解决方案8】:

            对不起,我在弄错之前给出的例子。这不是正确的版本:

            class Wheel
            {
                public void RepairWhell() { }
            }
            
            class Chassis
            {
                public void RepairChassis() { }
            }
            
            class Engine
            {
                public void RepairEngine() { }
            }
            
            class CarWorkshop
            {
                public string Repair(Wheel value)
                {
                    value.RepairWhell();
                    return "wheel repaired";
                }
                public string Repair(Chassis value)
                {
                    value.RepairChassis();
                    return "chassis repaired";
                }
                public string Repair(Engine value)
                {
                    value.RepairEngine();
                    return "engine repaired";
                }
            }
            
            class Program
            {
                static void Main(string[] args)
                {
                    var carWorkshop = new CarWorkshop();
            
                    dynamic whell = new Wheel();
                    dynamic chassis = new Chassis();
                    dynamic engine = new Engine();
            
                    Console.WriteLine(carWorkshop.Repair(whell));
                    Console.WriteLine(carWorkshop.Repair(chassis));
                    Console.WriteLine(carWorkshop.Repair(engine));
                    Console.ReadLine();
                }
            }
            

            所以答案是肯定的。 C# 提供多分派。

            【讨论】:

              【解决方案9】:

              您可以使用动态关键字在 C# 中实现多分派。

              interface IA { }
              interface IB { }
              class CA1 : IA {}
              class CA2 : IA {}
              class CA11 : CA1 {}
              class CB1 : IB {}
              class CB2 : IB {}
              
              class MD
              {
                  public enum X { X } ;
                  public static void Foo(IA a, IB b, X dispatch = X.X) { Foo((dynamic)a, (dynamic)b); }
                  static void Foo(IA a, IB b) { Console.WriteLine("IA IB"); }
                  static void Foo(CA1 a, CB1 b) { Console.WriteLine("CA1 CB1"); }
                  static void Foo(CA2 a, CB1 b) { Console.WriteLine("CA2 CB1"); }
                  static void Foo(CA1 a, CB2 b) { Console.WriteLine("CA1 CB2"); }
                  static void Foo(CA2 a, CB2 b) { Console.WriteLine("CA2 CB2"); }
                  static void Foo(CA11 a, CB2 b) { Console.WriteLine("CA11 CB2"); }
              }
              class Program
              {
                  static void Main(string[] args)
                  {
                      var a1 = new CA1();
                      var a11 = new CA11();
                      var a2 = new CA2();
                      var b1 = new CB1();
                      var b2 = new CB2();
                      MD.Foo(a1, b1);
                      MD.Foo(a2, b1);
                      MD.Foo(a1, b2);
                      MD.Foo(a2, b2);
                      MD.Foo(a11, b1);
                      MD.Foo(a11, b2);
                  }
              }
              

              Foo((dynamic)a,(dynamic)b)) 的解析是在运行时完成的,并根据 'a' 和 'b' 的具体类型选择重载的 Foo 方法之一。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2021-06-16
                • 2020-09-19
                • 2015-01-16
                • 2022-01-20
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多