Friday, April 27, 2012

Multiple dispatch in C#

This code...

using System;


public class MainClass {

    public static void Main() {
        Asset[] xx = { new Asset(), new House(), new Asset(), new House() };

        foreach(Asset x in xx) {
            Foo(x);
        }
    }

        
    public static void Foo(Asset a) {
        Console.WriteLine("Asset");
    }

    public static void Foo(House h) {
        Console.WriteLine("House");
    }

}


public class Asset {
}

public class House : Asset {
}



...outputs:

Asset
Asset
Asset
Asset


If you want an output of Asset, House, Asset, House, i.e. you want to use the overloaded method that matches the object type(House) not by object's reference type(on this expression, Asset is the reference type: foreach(Asset x in xx), we are using Asset as a reference for some of the House object in array of Asset object), there are multiple approach to solve the problem, one is to design your class polymorphism by using virtual and override; another is to amend the Foo(Asset a) code.
Another way is to use dynamic of C# 4, this is covered on later part of this post

Let's try with this one:
public static void Foo(Asset a) {
    if (a.GetType() == typeof(Asset))
        Console.WriteLine("Asset");
    else if (a.GetType() == typeof(House))
        Foo((House) a);
}

That's too much for asking on the developer of Foo(Asset a). What if there's another class that derives Asset? Say Car, that would entail adding another else if (a.GetType() == typeof(Car)), and what's more difficult is you cannot know in advanced what new classes will derive from Asset. And that approach is an antithesis of http://www.antiifcampaign.com/

You can call the runtime-matched method by using reflection for those purposes:

using System;


public class MainClass {

    public static void Main() {
        Asset[] xx = { new Asset(), new House(), new Asset(), new House(), new Car() };

        foreach(Asset x in xx) {
            Foo(x);
        }
    }

        
    public static void Foo(Asset a) {
 
        if (a.GetType() == typeof(Asset))
            Console.WriteLine("Asset");
        else {
            Type t = typeof(MainClass);
            t.InvokeMember("Foo", System.Reflection.BindingFlags.InvokeMethod, null, 
                t, new object[] { a } ); 
        }

    }

    public static void Foo(House h) {
        Console.WriteLine("House");
    }

    public static void Foo(Car c) {
        Console.WriteLine("Car");
    }

}


public class Asset {
}

public class House : Asset {
}

public class Car : Asset {
}


Outputs:
Asset
House
Asset
House
Car


If you are using C# 4, you can use dynamic to avoid resorting to reflection, and code-wise it has less friction(if you don't treat casting to dynamic as friction) on your code, i.e. you don't need to modify the Foo(Asset a), dynamic feature is worth considering if Foo helpers comes in binary form, i.e. no source code.

using System;


public class MainClass {

    public static void Main() {
        Asset[] xx = { new Asset(), new House(), new Asset(), new House(), new Car() };

        foreach(Asset x in xx) {
            Foo((dynamic)x);
        }
    }

        
    public static void Foo(Asset a) {
        Console.WriteLine("Asset");
    }

    public static void Foo(House h) {
        Console.WriteLine("House");
    }

    public static void Foo(Car c) {
        Console.WriteLine("Car");
    }

}


public class Asset {
}

public class House : Asset {
}

public class Car : Asset {
}

Outputs:
Asset
House
Asset
House
Car


Another good thing with multiple dispatch via dynamic as compared to reflection, dynamic multiple dispatch is similar with polymorphism that happens on recommended class polymorphism(i.e. one that uses virtual & override), i.e. if there's no available overloaded static method for a given derived class, dynamic will automatically find a method that best matches the given derived class, and what best matches the derived class than its base class? Nice, isn't it? :-)

So in our code example, if we remove the public static void Foo(Car c) from the code above, the Car type will be resolved by dynamic to invoke Foo Asset overload instead, the output is:

Asset
House
Asset
House
Asset


Whereas if you use static method invocation using reflection, you don't have that kind of luxury, the output for reflection approach when public static void Foo(Car c) is not available:

Asset
House
Asset
House


Notice the absence of 5th output? Your code will just fail silently if you just have the kind of reflection code above.

If you want to use the Foo Asset overload for Car type on absence of Foo Car overload, you have to find a way how reflection can invoke the base type overload given the absence of actual type overload, and perhaps a bit of google-fu or stackoverflow-fu could help you find a solution for this problem. Please advise me if you find out how ツ

No comments:

Post a Comment