我听过/读过这个词,但不太明白这意味着什么。
我什么时候应该使用这种技术,我将如何使用它?谁能提供一个好的代码示例?
访问者模式是一种以面向对象的方式进行双重调度的方式。
当您希望根据运行时的类型而不是编译时选择给定参数使用哪种方法时,它非常有用。
双重调度是多次调度的特例。
当您在对象上调用虚方法时,这被认为是单调度,因为调用哪个实际方法取决于单个对象的类型。
对于双重调度,将考虑对象的类型和方法唯一参数的类型。这类似于方法重载解析,除了参数类型在运行时以双分派确定,而不是在编译时静态确定。
在多分派中,方法可以传递多个参数,使用哪个实现取决于每个参数的类型。评估类型的顺序取决于语言。在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).
}
}
好吧,嘿伙计们,马克发布的代码并不完整,无论什么都没有用。
所以调整和完成。
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
。