C#中的双重调度?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/42587/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Double dispatch in C#?
提问by Oded
I have heard/read the term but don't quite understand what it means.
我听说过/读过这个词,但不太明白它的意思。
When should I use this technique and how would I use it? Can anyone provide a good code sample?
我什么时候应该使用这种技术,我将如何使用它?谁能提供一个好的代码示例?
采纳答案by Mark Cidade
The visitor pattern is a way of doing double-dispatch in an object-oriented way.
访问者模式是一种以面向对象的方式进行双重调度的方式。
It's useful for when you want to choose which method to use for a given argument based on its type at runtime rather than compile time.
当您想在运行时而不是编译时根据给定参数的类型选择用于给定参数的方法时,它很有用。
Double dispatch is a special case of multiple dispatch.
双分派是多分派的特例。
When you call a virtual method on an object, that's considered single-dispatch because which actual method is called depends on the type of the single object.
当您在对象上调用虚拟方法时,这被视为单分派,因为调用的实际方法取决于单个对象的类型。
For double dispatch, both the object's type and the method sole argument's type is taken into account. This is like method overload resolution, except that the argument type is determined at runtime in double-dispatch instead of statically at compile-time.
对于双重分派,对象的类型和方法唯一参数的类型都被考虑在内。这类似于方法重载解析,除了参数类型是在运行时以双分派方式确定的,而不是在编译时静态确定的。
In multiple-dispatch, a method can have multiple arguments passed to it and which implementation is used depends on each argument's type. The order that the types are evaluated depends on the language. In LISP, it checks each type from first to last.
在多分派中,一个方法可以有多个参数传递给它,使用哪个实现取决于每个参数的类型。评估类型的顺序取决于语言。在 LISP 中,它从头到尾检查每种类型。
Languages with multiple dispatch make use of generic functions, which are just function delcarations and aren't like generic methods, which use type parameters.
具有多重分派的语言使用泛型函数,泛型函数只是函数声明,与使用类型参数的泛型方法不同。
To do double-dispatch in C#, you can declare a method with a sole object argument and then specific methods with specific types:
要在 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).
}
}
回答by Zenwalker
The code posted by Mark isn't complete and what ever is there isn't working.
Mark 发布的代码不完整,并且那里的代码不起作用。
So tweaked and complete.
如此调整和完成。
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();
}
}
Thanks Mark and others for nice explanation on Double Dispatcher pattern.
感谢 Mark 和其他人对 Double Dispatcher 模式的精彩解释。
回答by Micha Wiedenmann
C# 4 introduces the pseudo type dynamic
which resolves the function call at runtime (instead of compile time). (That is, the runtime type of the expression is used). Double- (or multi-dispatch) can be simplified to:
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"
}
Note also by using dynamic
you prevent the static analyzer of the compiler to examine this part of the code. You should therefore carefully consider the use of dynamic
.
还要注意通过使用dynamic
你防止编译器的静态分析器来检查这部分代码。因此,您应该仔细考虑使用dynamic
.
回答by Evsei Zholobov
Full listing of working code
工作代码的完整列表
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;
}
}
}
回答by MikeJ
The other answers use generics and the runtime type system. But to be clear the use of generics and runtime type system doesn't have anything to do with double dispatch. They can be used to implement it but double dispatch is just dependent on using the concrete type at runtime to dispatch calls. It's illustrated more clearly I think in the wikipedia page. I'll include the translated C++ code below. The key to this is the virtual CollideWith on SpaceShip and that it's overridden on ApolloSpacecraft. This is where the "double" dispatch takes place and the correct asteroid method is called for the given spaceship type.
其他答案使用泛型和运行时类型系统。但是要清楚泛型和运行时类型系统的使用与双重调度没有任何关系。它们可用于实现它,但双重调度仅依赖于在运行时使用具体类型来调度调用。我认为在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]);
}
}