什么是 - 单次和多次调度(与 .NET 相关)?

Posted

技术标签:

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

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

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

here提出了类似的问题

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

【问题讨论】:

【参考方案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 方法之一。

【讨论】:

以上是关于什么是 - 单次和多次调度(与 .NET 相关)?的主要内容,如果未能解决你的问题,请参考以下文章

任务调度之Timer与TimerTask配合

json 单次调用,相同功能的多次迭代

基于线程池的线程调度管控系统

基于线程池的线程调度管控系统

基于线程池的线程调度管控系统

服务器端的 C++ TCP 套接字多次写入()被视为客户端的单次读取