c# multiple dispatch options?
-
03-06-2021 - |
Question
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
) ?
Solution
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
dynamic
- Double Dispatch virtual methods
- Some hashed anonymous function rule collection
if (x is House) ... else if(x is Asset)...
- 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.
OTHER TIPS
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.
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 {
}