Advertisement
Advertisement


Double dispatch in C#?


Question

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?

2008/12/17
1
62
12/17/2008 4:39:27 AM

Accepted Answer

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).
    }
}       
2017/02/14
56
2/14/2017 10:14:31 PM

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.

2020/05/08

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.

2019/03/06

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]);
    }
}
2020/05/19

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;
        }
    }
}
2019/12/17

Source: https://stackoverflow.com/questions/42587
Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Email: [email protected]