So after my last embarrassment of an entry I thought I would try to redeem myself by posting a few ways we can get around the problem.
Bill, Matt and Dominic all posted valid solutions to the problem as I had specified, however I hadn't actually posted all the constraints.
- We don't know the type of a.
- We don't know all the types which implement IA and IB. Additional types could be loaded or generated at runtime.
- I want to pass a directly and maintain object identity.
- We don't own and thus can't change the implementation of AandB.
Normally in such a situation we would just upcast a, but 1 and 2 rule that out. I think the only remaining solution is to use Reflection.
[note: to simplify this entry, I've redefined AandB as static]
MethodInfo mi = typeof(Program).GetMethod("AandB", BindingFlags.Static | BindingFlags.NonPublic); mi = mi.MakeGenericMethod(a.GetType()); mi.Invoke(null, new object[] { a });
It works, it's concise and easy to understand, but on my system 50,000 invocations (across 3 different types of a) takes about 1.8 seconds. If that doesn't sound slow, consider that calling AandB directly the same number of times would appear to take less than a millisecond.
We can speed things up a little through caching. If we hold on to the MethodInfo for AandB we drop to about 1 second. If we cache the generic MethodInfo for each type in a Dictionary<Type, MethodInfo>, we can get down to about 350 milliseconds. But that's still too slow for me.
One way to speed things up is with our new friend DynamicMethod.
delegate void ObjectDelegate(object o); static ObjectDelegate callAandB = new ObjectDelegate(Rewrite);
I'm going to wrap up the call inside a delegate that just takes an object. This is initialised with the Rewrite method which you'll see soon. Now our situation looks like this:
static void FastSituation(IA a) { if (a is IB) { callAandB(a); } }
And, as for Rewrite:
static List<Type> types = new List<Type>(); static void Rewrite(object o) { types.Add(o.GetType()); MethodInfo rewrite = typeof(Program).GetMethod("Rewrite", BindingFlags.Static | BindingFlags.NonPublic); MethodInfo target = typeof(Program).GetMethod("AandB", BindingFlags.Static | BindingFlags.NonPublic); DynamicMethod dynamic = new DynamicMethod("", null, new Type[] { typeof(object) }, typeof(Program)); ILGenerator gen = dynamic.GetILGenerator(); foreach (Type type in types) { Label next = gen.DefineLabel(); MethodInfo genericTarget = target.MakeGenericMethod(type); gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Isinst, type); gen.Emit(OpCodes.Dup); gen.Emit(OpCodes.Ldnull); gen.Emit(OpCodes.Beq, next); gen.EmitCall(OpCodes.Call, genericTarget, null); gen.Emit(OpCodes.Ret); gen.MarkLabel(next); gen.Emit(OpCodes.Pop); } gen.Emit(OpCodes.Ldarg_0); gen.EmitCall(OpCodes.Call, rewrite, null); callAandB = (ObjectDelegate) dynamic.CreateDelegate(typeof(ObjectDelegate)); callAandB(o); }
The premise of Rewrite is that it will replace the callAandB delegate with a specalised DynamicMethod which can cast instances to their actual type before calling AandB directly. The DynamicMethod that's being generated here would look something like this in C#:
void Method(object o) { Class1 class1 = o as Class1; if (o != null) { AandB(class1); } else { Class2 class2 = o as Class2; if (o != null) { AandB(class2); } else { Rewrite(o); } } }
The generated IL is slightly different in that it doesn't need all those locals, but the result is the same. This looks practically identical to the solution Dominic posted, the difference is that we're generating the code at runtime.
This seems to take us back to the sweet-spot, with 50,000 invocations back under a millisecond.
