关于设计模式:C#中的双重调度?

关于设计模式:C#中的双重调度?

Double dispatch in C#?

我听过/读过这个词,但不太明白它的意思。

何时应使用此技术,我将如何使用它?任何人都可以提供良好的代码示例吗?


访问者模式是一种以面向对象的方式进行两次调度的方法。

当您要在运行时而不是编译时根据其类型选择给定参数使用哪种方法时,它很有用。

双重调度是多重调度的一种特殊情况。

当您在对象上调用虚拟方法时,这被认为是单调度,因为调用哪个实际方法取决于单个对象的类型。

对于双重分派,将同时考虑对象的类型和方法sole参数的类型。这类似于方法重载解析,只是参数类型是在运行时以双调度方式而不是在编译时静态确定的。

在多调度中,一个方法可以将多个参数传递给它,并且使用哪种实现取决于每个参数的类型。评估类型的顺序取决于语言。在LISP中,它从头到尾检查每种类型。

具有多个分派的语言使用泛型函数,这些泛型函数只是函数代用,与使用类型参数的泛型方法不同。

要在C#中进行双调度,可以声明一个带有唯一对象参数的方法,然后声明具有特定类型的特定方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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)发布的代码不完整,无法正常运行。

如此调整并完整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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,该伪类型在运行时(而不是编译时)解析函数调用。 (即,使用表达式的运行时类型)。双(或多调度)可以简化为:

1
2
3
4
5
6
7
8
9
10
11
12
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


其他答案使用泛型和运行时类型系统。但是需要明确的是,泛型和运行时类型系统的使用与双重调度没有任何关系。可以使用它们来实现它,但是双重调度仅依赖于在运行时使用具体类型来调度调用。我认为在Wikipedia页面上可以更清楚地说明。我将在下面包含翻译后的C代码。这样做的关键是在SpaceShip上虚拟的CollideWith,并且在ApolloSpacecraft上已被覆盖。在此进行"双"调度,并为给定的飞船类型调用正确的小行星方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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]);
    }
}

完整的工作代码清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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;
        }
    }
}


推荐阅读