是否与重载相同,如果不是,请您提供每个 C# 中的示例
我已经阅读了对 SO 中提出的类似问题的回复......我不明白发布给它的回复。
类似的问题在这里问
编辑:在 C# 4.0 中使用新的“动态”关键字......这会使语言“多调度”启用吗?
是否与重载相同,如果不是,请您提供每个 C# 中的示例
我已经阅读了对 SO 中提出的类似问题的回复......我不明白发布给它的回复。
类似的问题在这里问
编辑:在 C# 4.0 中使用新的“动态”关键字......这会使语言“多调度”启用吗?
多重分派是重载的一种“形式”......
例如,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);
然后我们将调用Process(Stream)方法,Derived
因为 C# 对对象指针 (b) 进行了一次分派,然后使用编译时间信息来决定调用哪个方法。即使流是 MemoryStream,单个调度系统也会忽略这一点。
在多调度系统中,将查看对象指针(如在 C# 中)并检查参数的运行时类型。在上面的例子中,因为流实际上是一个 MemoryStream 系统会调用Process(MemoryStream)方法。
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:显然,这不是真的。
多分派与方法重载有关,但并不相同。前者是动态运行时决策,后者是静态编译时决策。隐含了灵活性的好处,但因此多分派的性能成本。
AFAIK 一种语言可以支持任何一种,但不能同时支持两者,尽管两者都可以模拟(可以与访问者一起模拟多调度)。C# 在编译时确定数据类型,因此不是多调度语言,因此不可能有示例。
(警告:我不是 100% 的)
附录:实际上 wikipedia 有一篇关于这方面的文章,看起来非常详尽,哦,还有一个有用的 LISP 示例
单/多分派是一种运行时重载。单一调度通常被称为虚函数。调用虚函数时调用的确切函数取决于对象的运行时类型。双分派是同样的事情,扩展为使用两个对象——通常是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)
在 OO 语言中,文本:
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。
#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++ 无法在运行时确定 aPet
实际上是 aCat
还是 a Dog
。
如果在运行时有某种方法可以做到这一点(这样上面的代码将在未注释注释行的情况下编译),C++ 将被称为支持多调度或多方法。
在 c# 4.0 中,使用新的动态关键字启用了多方法:
使用系统;命名空间示例 { 类 Wheel { public void 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#中动态引入多方法,一个非常强大的范例。
对不起,我之前给出的例子是错误的。那不是正确的版本:
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# 提供多分派。
您可以使用 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((dynamic)a,(dynamic)b)) 的解析是在运行时完成的,并根据 'a' 和 'b' 的具体类型选择重载的 Foo 方法之一。