Method Invocation based on Runtime Type of Parameter (conclusion)

In the last two installments, we covered both a simple baseline implementation of
the double dispatch pattern and a flexible method of passing additional parameters.
This leaves us with one remaining objective – being able to invoke a specific method
based on the runtime type of multiple parameters. To start off we make the observation
that the following code:

void Sample<T1>( Thing a, Thing  b, T1 data)

 

can be logically transformed to and from

void Sample<T1>( Thing b, ref  T2< Thing,T1> encapsulatedData)

 

by simply creating a simple wrapper contain such as the one below:

class T2<TARGET, ARG2, DATA>
{
    public T2(TARGET target, ARG2 arg2, DATA originalData)
    {
        Target = target;
        Arg2 = arg2;
        OriginalData = originalData;
    }

  internal TARGET Target { get; private set; }
   internal ARG2 Arg2 { get; private set; }
   internal DATA OriginalData { get; private set; }
}

class Helper<X> : IThingDispatchTarget<T2<X,MyData>> where X : Thing
{
   public void Dispatch( Thing b, ref  T2<X, MyData> encapsulatedData) {…}
}

We already know how to perform a a method invocation based on runtime type for a method with the signature of “EncapsulatedSample” so it would seem we are very close
to our goal. In fact, if we were dealing with C++ templates rather than C# generics,  we would indeed by on the last steps of our journey. Unfortunately, even though our T2 helper class is specialized for a specific derived class, within the generic body, the type is still treated as if it were of the type specified in the where clause. Thus any usage of encapsulatedData.FirstParameter within our Dispatch(…) method [or indeed within the Helper class implementation] will be treaded as a Thing rather than a specific derived class such as: Asteroid or Planet. So much for being able to simply dispatch using the methodology we have been covering.

Alas, there is no way to accomplish our goal using purely “static” code while avoiding
significant duplication, and/or runtime conditional code. This series would not be a useful “Power Programming” tip if it ended here, and it doesn’t. Compiled Expression Trees can come to our rescue! The following code will generate a delegate that will invoke the proper overload:

private staticAction<TARGET, ARG1, ARG2, DATA> GetRealAction<TARGET,ARG1, ARG2, DATA>()
{    
   MethodInfo
mi = typeof(TARGET).GetMethod(
            “Execute”
             new[] {typeof(ARG1),typeof(ARG2),typeof(DATA)});     
   ParameterExpression target = Expression.Paramete(typeof(TARGET),target”); 

   ParameterExpression arg1 = Expression.Paramete(typeof(ARG1),“arg1”);

   ParameterExpression arg2 = Expression.Parameter( typeof(ARG2),“arg2”);

   ParameterExpression data =  Expression.Parameter( typeof(DATA),“data”);     

   MethodCallExpression mce = Expression.Call(

                                target, mi, arg1, arg2, data);    

   Expression<Action<TARGET, ARG1, ARG2, DATA>> lambda =    

              Expression.Lambda<Action<TARGET, ARG1, ARG2, DATA>>

             (mce, target, arg1, arg2, data);    
   return
lambda .Compile();
}

With this helpful utility method, we can create all that we need as part of the
type initialization of the Helper class:

static Helper()

{
    sr_ThingAction = GetRealAction<RealClass, X, ThingMyData>();

  sr_SpaceshipAction = GetRealAction< RealClass, X,  Spaceship, MyData>();

    sr_PlanetAction = GetRealAction< RealClass, X, Planet, MyData>();    
    sr_SunAction = GetRealAction< RealClass, X, Sun, MyData>();
    sr_AsteroidAction = GetRealAction< RealClass, X, Asteroid,MyData>();
}  
private static readonly Action<RealClass, X, Thing,MyData> sr_ThingAction;

private static readonly Action<RealClass, X, Spaceship, MyData> sr_SpaceshipAction; private static readonly Action<RealClass, X, Planet, MyData> sr_PlanetAction; private static readonly Action<RealClass, X, Sun, MyData> sr_SunAction;

private static readonly Action<RealClass, X, Asteroid, MyData> sr_AsteroidAction;

 

And complete our Helperclass implementation with:

public void Dispatch( Thing b, ref T2<RealClass, ARG1, MyData> data)
{ b.Dispatch( thisref data); }  

 

public void Execute(Thing instance, ref  T2<RealClass,ARG1,MyData>data)

{ sr_ThingAction(data.Target,data.Arg1, instance, data.OriginalData); }

 

public void Execute(Spaceship instance, ref T2<RealClass, ARG1, MyData>data) { sr_SpaceshipAction(data.Target, data.Arg1,instance,data.OriginalData); }

 
public
void Execute(Planet instance, ref T2< RealClass, ARG1, MyData>data)

 { sr_PlanetAction(data.Target, data.Arg1, instance, data.OriginalData); }

public void Execute( Sun instance, ref T2< RealClass,ARG1, MyData> data)
{ sr_SunAction(data.Target, data.Arg1, instance, data.OriginalData); }  

 

public void Execute( Asteroid instance, ref T2<RealClass, ARG1, MyData> data) { sr_AsteroidAction(data.Target, data.Arg1, instance, data.OriginalData); }

 

Conclusion

Well, it took a bit of work but we finally made it. The only remaining question is was the effort worth it. Running the above code where we have 1,000 elements (of equally distributed derived types) and running a loop that invokes a method for every combination (1,000,000 method invocations), gives the following numbers:

Scenario Total Time (mSec) Methodology Overhead(mSec)
Baseline loop directly calling a method which takes the base (
Thing
) type as parameters
738  
Multi-Dispatch using the above architecture 9510 8772
Multi-Dispatch using the dynamic keyword 18478 17739

The chart clearly shows that dispatching to specific methods based on runtime type
of parameter is not “cheap”, but the timing difference is significant. Using dynamic created double the overhead of out multi-dispatch implementation, and we reduced the loop time from over 18.4  seconds to 9.5 seconds. If this were a smaller collection (say 1000 invocations per iteration) this would translate to a difference in frame rates (assuming this  loop was the only calculation occurring) of 54 FPS and 105 FPS.

Additionally, although the use cases where this implementation is worth the effort may be small; we did uncover a very powerful technique for being able to tread generic arguments as their actual type rather than being constrained to the functionality exposed by the common base [i.e. the type specified in the where clause]. In the long run is is the discovery of these “gems” that are potentially applicable to a wide variety of use-cases that provides the biggest gains in exploring “Power Programming” techniques.

I hope you have all enjoyed this journey, and am looking forward to hearing your
comments and feedback. The next series of posts will be focused back on ALM, but
I am hoping to produce about one “Power Programming” series per month.

Advertisements

About David V. Corbin

President / Chief Architect Dynamic Concepts Development Corp. Microsoft MVP 2008-2011 (current Specialized in ALM) Microsoft ALM Ranger 2009-2011
This entry was posted in .NET Architecture & Implementation. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s