什么是 - 单次和多次调度(与 .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 相关)?的主要内容,如果未能解决你的问题,请参考以下文章