c# multiple dispatch options?
Asked Answered
D

3

6

I have these classes :

class Asset
{    }

class House:Asset
{    }

consider these outsiders static functions :

static void Foo (Asset a) { }
static void Foo (House h) { }

If i write :

House h = new House (...); 
Foo(h);

it will call Foo(House)(compile time binding)

if i write :

Asset a = new House (...);
Foo(a); 

it will call Foo(Asset) (compile time binding)

goal : access the runtime type method :

I have 2 options :

1) using dynamic like this :

 Asset a = new House (...);
 Foo ((dynamic)a); // NOW it will call  Foo(House)

2) move the functions from static to override using polymorphism mechanism.

question :

is there any other way of doing it ( without moving the functions to polymorphism mechanism || dynamic) ?

Dilapidate answered 26/4, 2012 at 9:36 Comment(15)
Asset a = new House (...); will not call Foo(Asset), cause runtime type is House, so it will call Foo(House)Claman
@Tigran: Overload resolution is performed at compile time. Asset a = new House(...); Foo(a); will call Foo(Asset) because a is declared as Asset.Fetish
@dtb: there is no any overloading these are static methods, and it will not call Foo(Asset), and even if there is the sence of overloading is runtime type identification, and runtime type in the string Asset a = new House() is House.Claman
@Tigran: There are two methods with the name Foo that differ in their list of parameters. Overloading "is what happens when you have two methods with the same name but different signatures. At compile time, the compiler works out which one it's going to call, based on the compile time types of the arguments and the target of the method call." Matters are different if there was an instance-method virtual void Foo in Asset and an instance-method override void Foo in House (Overriding).Fetish
@Claman ive written outsiders static functions dtb is rightDilapidate
@RoyiNamir: Double dispatch is done in single dispatch languages like C# usually using the visitor pattern. I guess that's what you mean by "polymorphism mechanism"?Fetish
@dtb: I'm messed your comment before with oevrriding, you are right. :) But, by the way, it will not call Foo(Asset), cause runtime type is House.Claman
@RoyiNamir: What is right? Did you try to run your code Asset a = new House(); Foo(a) ? And you see that Foo(Asset) called?Claman
@Claman this code is taken from a book ( c# in nutshell). it gives 2 options to solve it. I asked if theresis more. and without checking it - it does run.Dilapidate
@RoyiNamir: well you can try to run your second case and you will see that it does exactly the same what does first one. The only difference that in second case you specify A like a holder, and not concrete House type, but runtime type of both of them is exaclty the same, which leads to exactly same behavior. Why am I pointing out this? Cause there is no any dispatch in this example.Claman
@Tigran: Here's a short but complete example program: class Asset { } class House : Asset { } class Program { static void Main() { Asset a = new House(); Foo(a); } static void Foo(Asset a) { Console.WriteLine("Asset"); } static void Foo(House h) { Console.WriteLine("House"); }. It prints Asset. Because there is no dispatch are runtime. Just overload resolution at compile time.Fetish
@tigran no it doesnt .its just like in my examples ive just tested it.Dilapidate
This ruin my notion of burned-in(compile-time resolution) instruction: Foo((dynamic)a); I'm thinking what kind of magic is happening under-the-hood of that :-)Credence
@MichaelBuen: The magic is: the compiler emits code that starts the C# compiler again at runtime. The runtime version of the compiler analyzes the call as though the compile-time types of all the objects had been their actual runtime types, generates an expression tree representing that call, compiles the expression tree, caches the delegate for next time, and runs the delegate. The second time you hit this call site, it just calls the delegate.Willianwillie
@EricLippert C# compiler is wonderful and their principals too, comforting to know that it caches the delegate so the subsequent calls shall be faster. Thanks for explaining the computer science behind it, +1 for reminding the term: call site. I was supposed to phrase burned-in instructions at call site for pondering the kind of magic behind dynamic :-)Credence
I
9

goal : access the runtime type method

That's what the dynamic keyword is there for. It's actually a pretty really clean & fast way to do multiple dispatch.

Your ultimate options for Multiple Dispatch are

  1. dynamic
  2. Double Dispatch virtual methods
  3. Some hashed anonymous function rule collection
  4. if (x is House) ... else if(x is Asset)...
  5. Reflection -- really slow and ugly

question : is there any other way of doing it ( without moving the functions to polymorphism mechanism || dynamic) ?

So Yes, there are ways of doing that take a lot of work on your part when you could just use dynamic which is fast, less error prone, and really clean syntax wise.

Icehouse answered 26/4, 2012 at 13:9 Comment(1)
A good compilation of possible solutions.. Thanks.. I have a similar solution mentioned in #21262019Tetragram
C
1

I think this is what's happening under the hood of Foo((dynamic)a):

Asset a = new House();

Type t = typeof(MainClass);
t.InvokeMember("Foo", 
     System.Reflection.BindingFlags.InvokeMethod, null, 
     t, new object[] { a });

That will resolve to Foo(House h)


A quick trip to monodis.exe, without using reflection(e.g. InvokeMember), i.e. using dynamic keyword instead, Asset a = new House(); Foo((dynamic)a), this is the IL:

IL_0025:  ldstr "Foo"
IL_002a:  ldnull 
IL_002b:  ldtoken MainClass
IL_0030:  call class [mscorlib]System.Type class [mscorlib]System.Type::GetTypeFromHandle(valuetype [mscorlib]System.RuntimeTypeHandle)

Pretty much what's your hunch shall tell you, the "Foo" is a dead giveaway that dynamic is reflection-y kind of business.

Now, this is sans dynamic, i.e. Asset a = new House(); Foo(a):

IL_0010:  ldloc.0 
IL_0011:  call void class MainClass::Foo(class Asset)

The burned instruction is pretty much decided, will not change, it always resolve to Foo(Asset);

Here's the complete code you can use to analyze the dynamic behavior (via monodis.exe or ildasm.exe):

using System;

public class MainClass {
    public static void Main() {

        Console.WriteLine("Hei");

        Asset a = new House();        
        Foo(a);    
        Foo((dynamic)a);  

        object x = 7;
        Foo((dynamic)x);
    }

    public static void Foo(House h) { Console.WriteLine("House"); }
    public static void Foo(Asset a) { Console.WriteLine("Asset"); }
    public static void Foo(int i) { Console.WriteLine("int"); }
}


public class Asset {
}

public class House : Asset {
}

Output:

Hei
Asset
House
int

This will invoke the Foo overload int, i.e. Foo(int i):

object x = 7;
t.InvokeMember("Foo", System.Reflection.BindingFlags.InvokeMethod, null, 
   t, new object[] { x } ); 

This would too:

t.InvokeMember("Foo", System.Reflection.BindingFlags.InvokeMethod, null, 
   t, new object[] { 8 } ); 

So on your question, what other option you can use, you can use a method that accepts a an untyped object:

public static void FooDynamic(object o) 
{
    Type t = typeof(MainClass);
    t.InvokeMember("Foo", System.Reflection.BindingFlags.InvokeMethod, null, t, new object[] { o } ); 
}

To invoke:

Asset a = new House();
FooDynamic(a); // will select Foo House overload 

int i = 7;
FooDynamic(i); // will select Foo int overload

You can also use this API for the code above: public static void Foo(object o), then you would have to call Foo like this:

Asset a = new House();
Foo((object)a); // will resolve to House

Given that there's already a dynamic capability in C# 4, I would be hard-pressed to use reflection, unless the dev is still using C# 3. So there, use the dynamic approach instead :-)


UPDATE

For what it's worth, dynamic is slow (at least on Mono), when I run this code, there is a considerable delay before the letter "B" appear, about 2 seconds. The dynamic's delay is reproducible even I swap the code order of dynamic and reflection. Reflection's delay is imperceptible, it's faster than dynamic.

using System;

public class MainClass {

    public static void Main() {

        // there's a delay on initial dynamic call, about two seconds
        Test (); 
        Console.ReadLine ();

        // dynamic's speed is instant on subsequent calls, 
        // as clarified by Eric Lippert, the delegate is cached,
        // hence the elimination of delay on subsequent dynamic calls
        Test (); 

    }

    public static void Test() {

        Asset a = new House();

        Console.WriteLine("A");
        Foo((dynamic)a);  // there is a considerable delay here, the "B" string appears after two seconds

        Console.WriteLine ("B");        
        Type t = typeof(MainClass);
        t.InvokeMember("Foo", System.Reflection.BindingFlags.InvokeMethod, null, t, new object[] { a } ); 

        Console.WriteLine("C");

    }


    public static void Foo(House h) { Console.WriteLine("House"); }
    public static void Foo(Asset a) { Console.WriteLine("Asset"); }
    public static void Foo(int i) { Console.WriteLine("int"); }
}


public class Asset {
}

public class House : Asset {
}
Credence answered 26/4, 2012 at 12:49 Comment(5)
Under the hood it's doing crazy stuff that the compiler does to resolve methods static but with the runtime type, compiling on the fly and caching it for future use. Actually your reflection example wouldn't work with overloaded methods, your missing the binder used to pick the correct overload.Icehouse
au contrarie, it works :-) InvokeMember take care of the business of which overloaded method to invoke. See my editCredence
Sorry you are correct, InvokeMember works in this case, it's more complicated cases in which it doesn't. But Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember which is what the compiler actually swaps out when you use the dynamic handles more cases than that reflection method, and does emit IL at runtime to make it sometimes up to 100 faster than reflection.Icehouse
@Icehouse there is a perceptible delay when using dynamic (at least on Mono C#), about two seconds; reflection is instant in my testing, faster than dynamic. See my UPDATE above. I will test the same code on Microsoft C# tomorrowCredence
let us continue this discussion in chatIcehouse
R
1

If you want the static entry point, but you also want the polymorphic behavior, then the easiest blend would be to use the singleton pattern. It will give you the static entry point, and the object it returns will have polymorphic methods.

I also suggest ignoring everyone that says a singleton is a horrible, bad thing. Prima Donna singleton woe-preaching is a shibboleth of mid-level developers. A singleton is just a tool, and if it fits your needs - then use it.

Rocketry answered 26/4, 2012 at 13:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.