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

时间:2009-01-26 16:43:31

标签: c# multiple-dispatch single-dispatch

是否与重载相同,如果没有,请您提供C#

中的每个示例

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

类似问题here

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

9 个答案:

答案 0 :(得分:5)

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:显然,事实并非如此。

答案 1 :(得分:5)

多次发送是重载的“形式”......

例如,C#是单一调度,因为如果根据一个参数计算出要调用的方法,则使用“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)方法。

答案 2 :(得分:2)

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

AFAIK语言既可以支持,也可以不支持两种语言,但两者都可以模拟(可以模拟多个调度访问者)。 C#在编译时确定数据类型,因此不是多调度语言,因此不可能有任何示例。

(警告:我不是百分之百)


附录:实际上维基百科上有一个article,看起来非常彻底,是一个有用的LISP示例

答案 3 :(得分:1)

单个/多个调度是一种运行时重载。单一调度通常称为虚拟功能。调用虚函数时调用的确切函数是根据对象的运行时类型决定的。双重调度是相同的,扩展到使用两个对象 - 通常是this参数和第二个参数。使用Vistor模式可以毫不费力地实现这一点。多个调度进一步将此概念扩展到更多参数,但在C#等语言中实现起来要困难得多(不是说它不能完成,而是很难)。有些语言开箱即用。

e.g。在.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)

答案 4 :(得分:1)

在OO语言中:

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

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

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

然后是单一调度,单点选择是与b相关联的类型系统。

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

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

答案 5 :(得分:0)

#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 ++支持多个调度或多方法。

答案 6 :(得分:0)

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

使用System; namespace例子 {     类轮     {         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)
    {
        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#中动态引入多方法,一种非常强大的范例。

答案 7 :(得分:0)

对不起,我在错误之前提供的例子。有正确的版本:

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#提供多分派。

答案 8 :(得分:0)

您可以使用dynamic关键字在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((动态)a,(动态)b))的分辨率在运行时完成,并根据“a”和“b”的具体类型选择一个重载的Foo方法。