Ode*_*ded 62 c# language-features design-patterns double-dispatch
我听过/读过这个词,但不太明白这意味着什么.
我什么时候应该使用这种技术,我将如何使用它?谁能提供一个好的代码示例?
Mar*_*ade 55
访问者模式是一种以面向对象的方式进行双重调度的方式.
当您希望根据运行时的类型而不是编译时选择给定参数使用哪种方法时,它非常有用.
双重调度是多次调度的特例.
当您在对象上调用虚方法时,这被认为是单调度,因为调用哪个实际方法取决于单个对象的类型.
对于双重调度,将考虑对象的类型和方法唯一参数的类型.这类似于方法重载解析,除了参数类型在运行时以双分派确定,而不是在编译时静态确定.
在多分派中,一个方法可以传递多个参数,并且使用哪个实现取决于每个参数的类型.评估类型的顺序取决于语言.在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).
}
}
Run Code Online (Sandbox Code Playgroud)
Zen*_*ker 11
好吧,嘿伙计们,马克发布的代码并不完整,什么都没有用.
所以调整和完成.
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();
}
}
Run Code Online (Sandbox Code Playgroud)
感谢Mark和其他人对Double Dispatcher模式的精彩解释
Mik*_*keJ 10
其他答案使用泛型和运行时类型系统。但需要明确的是,泛型和运行时类型系统的使用与双重分派没有任何关系。它们可以用来实现它,但双重调度仅依赖于在运行时使用具体类型来调度调用。我认为维基百科页面上的说明更清楚。我将在下面包含翻译后的 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]);
}
}
Run Code Online (Sandbox Code Playgroud)
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"
}
Run Code Online (Sandbox Code Playgroud)
还要注意通过使用dynamic
你防止编译器的静态分析器来检查这部分代码。因此,您应该仔细考虑使用dynamic
.