C# 中的双重调度?

发布于 2024-07-04 21:50:02 字数 77 浏览 15 评论 0原文

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

我什么时候应该使用这种技术以及如何使用它? 谁能提供一个好的代码示例吗?

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

一萌ing 2024-07-11 21:50:02

工作代码的完整列表

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;
        }
    }
}

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;
        }
    }
}
单身情人 2024-07-11 21:50:02

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"
}

另请注意,通过使用动态,您可以防止编译器的静态分析器检查这部分代码。 因此,您应该仔细考虑使用动态

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:

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.

财迷小姐 2024-07-11 21:50:02

其他答案使用泛型和运行时类型系统。 但需要明确的是,泛型和运行时类型系统的使用与双重分派没有任何关系。 它们可以用来实现它,但双重调度仅依赖于在运行时使用具体类型来调度调用。 我认为在维基百科页面中更清楚地说明了这一点。 我将在下面包含翻译后的 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]);
    }
}

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.

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]);
    }
}
暮年 2024-07-11 21:50:02

马克发布的代码不完整,并且其中的内容无法正常工作。

如此调整并完成。

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 和其他人对双调度程序模式的精彩解释。

The code posted by Mark isn't complete and what ever is there isn't working.

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.

无言温柔 2024-07-11 21:50:02

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

当您想要在运行时而不是编译时根据给定参数的类型选择使用哪个方法时,它非常有用。

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

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

对于双重分派,对象的类型和方法唯一参数的类型都会被考虑在内。 这类似于方法重载解析,只不过参数类型是在运行时以双重调度确定的,而不是在编译时静态确定的。

在多重分派中,一个方法可以传递多个参数,并且使用哪个实现取决于每个参数的类型。 评估类型的顺序取决于语言。 在 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).
    }
}       

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.

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:

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).
    }
}       
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文