views:

354

answers:

2

My question is this:

If I'm going to build a DynamicMethod object, corresponding to a ConstructorInfo.Invoke call, what types of IL do I need to implement in order to cope with all (or most) types of arguments, when I can guarantee that the right type and number of arguments is going to be passed in before I make the call?


Background

I am on my 3rd iteration of my IoC container, and currently doing some profiling to figure out if there are any areas where I can easily shave off large amounts of time being used.

One thing I noticed is that when resolving to a concrete type, ultimately I end up with a constructor being called, using ConstructorInfo.Invoke, passing in an array of arguments that I've worked out.

What I noticed is that the invoke method has quite a bit of overhead, and I'm wondering if most of this is just different implementations of the same checks I do.

For instance, due to the constructor matching code I have, to find a matching constructor for the predefined parameter names, types, and values that I have passed in, there's no way this particular invoke call will not end up with something it should be able to cope with, like the correct number of arguments, in the right order, of the right type, and with appropriate values.

When doing a profiling session containing a million calls to my resolve method, and then replacing it with a DynamicMethod implementation that mimics the Invoke call, the profiling timings was like this:

  • ConstructorInfo.Invoke: 1973ms
  • DynamicMethod: 93ms

This accounts for around 20% of the total runtime of this profiling application. In other words, by replacing the ConstructorInfo.Invoke call with a DynamicMethod that does the same, I am able to shave off 20% runtime when dealing with basic factory-scoped services (ie. all resolution calls end up with a constructor call).

I think this is fairly substantial, and warrants a closer look at how much work it would be to build a stable DynamicMethod generator for constructors in this context.

So, the dynamic method would take in an object array, and return the constructed object, and I already know the ConstructorInfo object in question.

Therefore, it looks like the dynamic method would be made up of the following IL:

l001:    ldarg.0      ; the object array containing the arguments
l002:    ldc.i4.0     ; the index of the first argument
l003:    ldelem.ref   ; get the value of the first argument
l004:    castclass T  ; cast to the right type of argument (only if not "Object")
(repeat l001-l004 for all parameters, l004 only for non-Object types,
 varying l002 constant from 0 and up for each index)
l005:    newobj ci    ; call the constructor
l006:    ret

Is there anything else I need to consider?

Note that I'm aware that creating dynamic methods will probably not be available when running the application in "reduced access mode" (sometimes the brain just won't give up those terms), but in that case I can easily detect that and just calling the original constructor as before, with the overhead and all.

+1  A: 

For value types step l004 should be l004: unbox.any T.

The easiest way to figure out the right IL you need to generate is to look at what is generated by the C# compiler using some test code.

static void Test(object[] args)
{
  TestTarget((string)args[0], (int)args[1], (DateTime?)args[2]);
}

static void TestTarget(string s, int i, DateTime? dt){}

compiles to:

L_0000: ldarg.0 
L_0001: ldc.i4.0 
L_0002: ldelem.ref 
L_0003: castclass string
L_0008: ldarg.0 
L_0009: ldc.i4.1 
L_000a: ldelem.ref 
L_000b: unbox.any int32
L_0010: ldarg.0 
L_0011: ldc.i4.2 
L_0012: ldelem.ref 
L_0013: unbox.any [mscorlib]System.Nullable`1<valuetype [mscorlib]System.DateTime>
L_0018: call void Program::TestTarget(string, int32, valuetype [mscorlib]System.Nullable`1<valuetype [mscorlib]System.DateTime>)
L_001d: ret 
Pent Ploompuu
So basically, since I have the Type object for every parameter, if the Type.IsValueType returns true, instead of castclass TYPE, I use unbox.any TYPE instead?
Lasse V. Karlsen
Yes, that should be enough to cover all parameter types.
Pent Ploompuu
Thanks, I'll add a bunch of unit tests to verify all the combinations I can think of, but this looks like it might just work :)
Lasse V. Karlsen
A: 

There are libraries available to make it easier (and faster) to work with reflection. For instance, Fasterflect can generate IL for invoking any constructor - all you need to do is pass it the arguments you want to use on the constructor.

// note: class must have constructor with (int,string,string) signature
object obj = someType.CreateInstance( new { id=1, name="jens", foo="bar" } );

The library is also capable of probing for an appropriate constructor to call, in case you don't have a set of parameters that exactly match a constructor.

// try to map id, name and foo to constructor parameters
// allows changing the order and permit fallback to setting fields/properties
// e.g. might result in call to ctor(string,string) and set field "id"
object obj = someType.TryCreateInstance( new { id=1, name="jens", foo="bar" } );

Disclaimer: I am involved in said project as a contributor.

Morten Mertner