Двойная отправка в C#?

Я слышал / читал термин, но не совсем понял, что это значит.

Когда я должен использовать эту технику и как я буду ее использовать? Кто-нибудь может предоставить хороший пример кода?

4 ответа

Решение

Шаблон посетителя - это способ выполнить двойную диспетчеризацию объектно-ориентированным способом.

Это полезно, когда вы хотите выбрать, какой метод использовать для данного аргумента, основываясь на его типе во время выполнения, а не во время компиляции.

Двойная отправка является частным случаем многократной отправки.

Когда вы вызываете виртуальный метод для объекта, это считается единой диспетчеризацией, потому что какой фактический метод вызывается, зависит от типа отдельного объекта.

Для двойной диспетчеризации учитывается как тип объекта, так и тип единственного аргумента метода. Это похоже на разрешение перегрузки метода, за исключением того, что тип аргумента определяется во время выполнения в двойной диспетчеризации, а не статически во время компиляции.

В множественной диспетчеризации метод может передавать несколько аргументов, и какая реализация используется, зависит от типа каждого аргумента. Порядок оценки типов зависит от языка. В 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();
    }
}

Спасибо Марку и другим за хорошее объяснение паттерна Double Dispatcher

В других ответах используются дженерики и система типов времени выполнения. Но чтобы было ясно, использование универсальных типов и системы типов времени выполнения не имеет ничего общего с двойной отправкой. Их можно использовать для реализации, но двойная отправка просто зависит от использования конкретного типа во время выполнения для отправки вызовов. Я думаю, это проиллюстрировано более ясно на странице википедии. Ниже я приведу переведенный код C++. Ключ к этому - виртуальное столкновение с космическим кораблем и то, что оно отменено на 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]);
    }
}

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 рассматривается как System.Object это никогда не позвонит void Foo(int x) в приведенном выше примере.

Обратите внимание, что с помощью dynamic Вы мешаете компилятору статических анализаторов исследовать эту часть кода. Вы должны тщательно рассмотреть вопрос об использовании dynamic,

Двойная отправка - это еще одно имя для шаблона посетителя.

У меня есть статья, которую я написал несколько лет назад об использовании Reflection для реализации шаблона посетителя. http://www.agileprogrammer.com/dotnetguy/articles/ReflectionVisitor.aspx

Полный список рабочего кода

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;
        }
    }
}
Другие вопросы по тегам