Compartilhar via


Multimethods in C# 4.0 with 'dynamic'

(Continuing from the post on implementing the Visitor pattern in C# 4.0)

A virtual method is polymorphic in one dimension (the runtime class of the object the method is attached to). Multimethods (multiple dispatch) are polymorphic in multiple dimensions, which can be very useful in some cases. The dynamic keyword in C# 4.0 allows for method selection that depends on the runtime class of the arguments, not just the attached object. This allows true multimethods. (Of course the lookup rules for dynamic are complex so extremely complex cases might not resolve the way you want).

The basic idea is to create method overloads which take all the (subclass1, subclass2) combinations that you are interested in and then have the (superclass, superclass) method cast its arguments to dynamic and then call the method again and let runtime resolution do its thing. In the spirit of the Wikipedia article on multimethods, here is a spaceship/asteroid collision example:

 (code edited to separate the names of the methods)

using System;

namespace MultiMethods
{
    class Program
    {
        class Thing { }
        class Asteroid : Thing { }
        class Spaceship : Thing { }

        static void CollideWithImpl(Asteroid x, Asteroid y)
        {
            Console.WriteLine("Asteroid hits an Asteroid");
        }

        static void CollideWithImpl(Asteroid x, Spaceship y)
        {
            Console.WriteLine("Asteroid hits a Spaceship");
        }

        static void CollideWithImpl(Spaceship x, Asteroid y)
        {
            Console.WriteLine("Spaceship hits an Asteroid");
        }

        static void CollideWithImpl(Spaceship x, Spaceship y)
        {
            Console.WriteLine("Spaceship hits a Spaceship");
        }

        static void CollideWith(Thing x, Thing y)
        {
            dynamic a = x;
            dynamic b = y;
            CollideWithImpl(a, b);
        }

        static void Main(string[] args)
        {
            var asteroid = new Asteroid();
            var spaceship = new Spaceship();
            CollideWith(asteroid, spaceship);
            CollideWith(spaceship, spaceship);
        }
    }
}

Comments

  • Anonymous
    August 19, 2009
    Nice - but wouldn't it be better to use a different method name to avoid infinite recursion? var x = new Thing(); var y = new Thing(); CollideWith(x, y); or: var x = new Asteroid(); var y = new Thing(); CollideWith(x, y);

  • Anonymous
    August 21, 2009
    Good point about the recursion. Using the same name is cute, but can easily lead to those problems. (Making Thing abstract would prevent this specific problem but leave the door open for future occurences).

  • Anonymous
    January 09, 2010
    Correct me if I'm wrong, but is it really a dynamic dispatch? in this example: var asteroid = new Asteroid(); var spaceship = new Spaceship(); CollideWith(asteroid, spaceship); CollideWith(spaceship, spaceship); overloads CollideWith(Asteroid x, Spaceship y) CollideWith(Spaceship x, Spaceship y) are called directly, and CollideWith(Thing x, Thing y) is never invoked

  • Anonymous
    October 27, 2010
    nikitazu - What you're missing is that the implementation methods are named CollideWithImpl, not CollideWith. If all of the methods had the same name then you would be correct. However, there is only one CollideWith method and it's defined for the base class Thing.