【发布时间】:2008-09-03 21:03:51
【问题描述】:
我听说过/读过这个词,但不太明白它的含义。
我应该什么时候使用这种技术以及如何使用它?谁能提供一个好的代码示例?
【问题讨论】:
-
Beware of the double dispatch。或许您可以避免它以更好地维护代码。
标签: c# design-patterns language-features double-dispatch
我听说过/读过这个词,但不太明白它的含义。
我应该什么时候使用这种技术以及如何使用它?谁能提供一个好的代码示例?
【问题讨论】:
标签: c# design-patterns language-features double-dispatch
访问者模式是一种以面向对象的方式进行双重调度的方式。
当您想根据运行时的类型而不是编译时的类型为给定参数选择使用哪种方法时,它很有用。
双重调度是多重调度的特例。
当您在对象上调用虚拟方法时,这被视为单次调度,因为调用的实际方法取决于单个对象的类型。
对于双重分派,对象的类型和方法唯一参数的类型都被考虑在内。这类似于方法重载解析,只是参数类型是在运行时以双重调度而不是在编译时静态确定的。
在多分派中,一个方法可以有多个参数传递给它,使用哪个实现取决于每个参数的类型。评估类型的顺序取决于语言。在 LISP 中,它会从头到尾检查每种类型。
具有多个分派的语言使用泛型函数,它们只是函数声明,不像泛型方法,后者使用类型参数。
要在 C# 中进行双重分派,您可以声明一个具有唯一对象参数的方法,然后声明具有特定类型的特定方法:
using System.Linq;
class DoubleDispatch
{
public T Foo<T>(object arg)
{
var method = from m in GetType().GetMethods()
where m.Name == "Foo"
&& m.GetParameters().Length==1
&& arg.GetType().IsAssignableFrom
(m.GetParameters()[0].GetType())
&& m.ReturnType == typeof(T)
select m;
return (T) method.Single().Invoke(this,new object[]{arg});
}
public int Foo(int arg) { /* ... */ }
static void Test()
{
object x = 5;
Foo<int>(x); //should call Foo(int) via Foo<T>(object).
}
}
【讨论】:
Mark 发布的代码不完整,以前的代码都不起作用。
如此调整和完整。
class DoubleDispatch
{
public T Foo<T>(object arg)
{
var method = from m in GetType().GetMethods(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic)
where m.Name == "Foo"
&& m.GetParameters().Length == 1
//&& arg.GetType().IsAssignableFrom
// (m.GetParameters()[0].GetType())
&&Type.GetType(m.GetParameters()[0].ParameterType.FullName).IsAssignableFrom(arg.GetType())
&& m.ReturnType == typeof(T)
select m;
return (T)method.Single().Invoke(this, new object[] { arg });
}
public int Foo(int arg)
{
return 10;
}
public string Foo(string arg)
{
return 5.ToString();
}
public static void Main(string[] args)
{
object x = 5;
DoubleDispatch dispatch = new DoubleDispatch();
Console.WriteLine(dispatch.Foo<int>(x));
Console.WriteLine(dispatch.Foo<string>(x.ToString()));
Console.ReadLine();
}
}
感谢 Mark 和其他人对 Double Dispatcher 模式的精彩解释。
【讨论】:
C# 4 引入了伪类型dynamic,它在运行时(而不是编译时)解析函数调用。 (即使用表达式的运行时类型)。双重(或多重调度)可以简化为:
class C { }
static void Foo(C x) => Console.WriteLine(nameof(Foo));
static void Foo(object x) => Console.WriteLine(nameof(Object));
public static void Main(string[] args)
{
object x = new C();
Foo((dynamic)x); // prints: "Foo"
Foo(x); // prints: "Object"
}
还要注意,使用dynamic 会阻止编译器的静态分析器检查这部分代码。因此,您应该仔细考虑使用dynamic。
【讨论】:
其他答案使用泛型和运行时类型系统。但要明确的是,泛型和运行时类型系统的使用与双重分派没有任何关系。它们可用于实现它,但双重调度仅依赖于在运行时使用具体类型来调度调用。我认为the wikipedia page 更清楚地说明了这一点。我将在下面包含翻译后的 C++ 代码。关键是 SpaceShip 上的虚拟 CollideWith,它在 ApolloSpacecraft 上被覆盖。这是“双重”调度发生的地方,并且为给定的飞船类型调用了正确的小行星方法。
class SpaceShip
{
public virtual void CollideWith(Asteroid asteroid)
{
asteroid.CollideWith(this);
}
}
class ApolloSpacecraft : SpaceShip
{
public override void CollideWith(Asteroid asteroid)
{
asteroid.CollideWith(this);
}
}
class Asteroid
{
public virtual void CollideWith(SpaceShip target)
{
Console.WriteLine("Asteroid hit a SpaceShip");
}
public virtual void CollideWith(ApolloSpacecraft target)
{
Console.WriteLine("Asteroid hit ApolloSpacecraft");
}
}
class ExplodingAsteroid : Asteroid
{
public override void CollideWith(SpaceShip target)
{
Console.WriteLine("ExplodingAsteroid hit a SpaceShip");
}
public override void CollideWith(ApolloSpacecraft target)
{
Console.WriteLine("ExplodingAsteroid hit ApolloSpacecraft");
}
}
class Program
{
static void Main(string[] args)
{
Asteroid[] asteroids = new Asteroid[] { new Asteroid(), new ExplodingAsteroid() };
ApolloSpacecraft spacecraft = new ApolloSpacecraft();
spacecraft.CollideWith(asteroids[0]);
spacecraft.CollideWith(asteroids[1]);
SpaceShip spaceShip = new SpaceShip();
spaceShip.CollideWith(asteroids[0]);
spaceShip.CollideWith(asteroids[1]);
}
}
【讨论】:
工作代码的完整列表
using System;
using System.Linq;
namespace TestConsoleApp
{
internal class Program
{
public static void Main(string[] args)
{
const int x = 5;
var dispatch = new DoubleDispatch();
Console.WriteLine(dispatch.Foo<int>(x));
Console.WriteLine(dispatch.Foo<string>(x.ToString()));
Console.ReadLine();
}
}
public class DoubleDispatch
{
public T Foo<T>(T arg)
{
var method = GetType()
.GetMethods()
.Single(m =>
m.Name == "Foo" &&
m.GetParameters().Length == 1 &&
arg.GetType().IsAssignableFrom(m.GetParameters()[0].ParameterType) &&
m.ReturnType == typeof(T));
return (T) method.Invoke(this, new object[] {arg});
}
public int Foo(int arg)
{
return arg;
}
public string Foo(string arg)
{
return arg;
}
}
}
【讨论】: