Click here to Skip to main content
15,891,657 members
Articles / Programming Languages / C#
Article

A General Fast Method Invoker

Rate me:
Please Sign up or sign in to vote.
4.96/5 (76 votes)
4 Jul 20061 min read 266K   3.1K   178   74
Method reflecting invoke is nice, but very frequently it can be too slow. This article describes an alternative method for dynamic method invoke.

Sample Image - FastMethodInvoker.gif

Introduction

Sometimes, I run across the need to dynamically invoke the method of an object, where the actual method might not be known until run-time. Usually, Reflecting is nice, but frequently doing it can be too slow. This article describes an alternative method for dynamic method invocation.

Background

When I read the article Fast Dynamic Property Accessors, I was thinking about my project, it has a lots of reflecting methods in circle. But it's methods not properties. But the DynamicMethod reminded me, maybe I could use Emit to generate a DynamicMethod to bind a special method before it can be invoked. I hope it will improve performance.

Using the Code

First, I reflected out the method which will be invoked:

C#
MethodInfo methodInfo = typeof(Person).GetMethod("Say");

Then, I get the MethodInvoker to invoke:

C#
FastInvokeHandler fastInvoker = GetMethodInvoker(methodInfo);
fastInvoker(new Person(), new object[]{"hello"});

Instead of using reflection method, invoke in the past:

C#
methodInfo.Invoke(new Person(), new object[]{"hello"});

Implementation

First, I need to define a delegate to adapt the dynamic method:

C#
public delegate object FastInvokeHandler(object target, 
                                   object[] paramters);

It looks the same as the class MethodInfo's Invoke method. Yes, that means I can write the same code to use it like in the past.

This code generates the DynamicMethod:

C#
public static FastInvokeHandler GetMethodInvoker(MethodInfo methodInfo)
{
    DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, 
                     typeof(object), new Type[] { typeof(object), 
                     typeof(object[]) }, 
                     methodInfo.DeclaringType.Module);
    ILGenerator il = dynamicMethod.GetILGenerator();
    ParameterInfo[] ps = methodInfo.GetParameters();
    Type[] paramTypes = new Type[ps.Length];
    for (int i = 0; i < paramTypes.Length; i++)
    {
        paramTypes[i] = ps[i].ParameterType;
    }
    LocalBuilder[] locals = new LocalBuilder[paramTypes.Length];
    for (int i = 0; i < paramTypes.Length; i++)
    {
        locals[i] = il.DeclareLocal(paramTypes[i]);
    }
    for (int i = 0; i < paramTypes.Length; i++)
    {
        il.Emit(OpCodes.Ldarg_1);
        EmitFastInt(il, i);
        il.Emit(OpCodes.Ldelem_Ref);
        EmitCastToReference(il, paramTypes[i]);
        il.Emit(OpCodes.Stloc, locals[i]);
    }
    il.Emit(OpCodes.Ldarg_0);
    for (int i = 0; i < paramTypes.Length; i++)
    {
        il.Emit(OpCodes.Ldloc, locals[i]);
    }
    il.EmitCall(OpCodes.Call, methodInfo, null);
    if (methodInfo.ReturnType == typeof(void))
        il.Emit(OpCodes.Ldnull);
    else
        EmitBoxIfNeeded(il, methodInfo.ReturnType);
    il.Emit(OpCodes.Ret);
    FastInvokeHandler invoder = 
      (FastInvokeHandler)dynamicMethod.CreateDelegate(
      typeof(FastInvokeHandler));
    return invoder;
}

Conclusion

Well, I think this is a general way that can be used instead of most of the reflection methods to get about 50 times performance improvement. Any suggestions for improvements are welcome.

Extra advantage (reminded by MaxGuernsey): If an exception occurs in your code, FastInovker would throw the original one, but the Method.Invoke would throw a TargetInvocationException.

History

  • 2006-7-05: Updated to add static method support. Thanks Manuel Abadia.
  • 2006-6-30: Updated to add ref/out parameter support. Thanks Roger for his nice suggestion.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
China China
I am currently working for a .NET framework names AgileFramework. It's introduction at here:
http://www.agilelabs.cn/agileframework

Now I'm living in China. I have been designing and developing .NET based software applications for 5+ years.

Comments and Discussions

 
QuestionNot necessay at all Pin
FatCatProgrammer22-Jan-13 8:25
FatCatProgrammer22-Jan-13 8:25 
AnswerRe: Not necessay at all Pin
Yasushi Irizawa9-Jun-13 20:06
Yasushi Irizawa9-Jun-13 20:06 
AnswerRe: Not necessay at all Pin
cajoncillos1-Jul-14 12:24
cajoncillos1-Jul-14 12:24 
GeneralRe: Not necessay at all Pin
FatCatProgrammer1-Jul-14 16:10
FatCatProgrammer1-Jul-14 16:10 
Questionabout your code's license Pin
qsmy12322-Aug-12 15:00
qsmy12322-Aug-12 15:00 
QuestionLicense Pin
Skeletor2323-Mar-12 5:05
Skeletor2323-Mar-12 5:05 
QuestionConstructorInfo Support. Pin
Member 455125623-Jun-10 7:29
Member 455125623-Jun-10 7:29 
AnswerRe: ConstructorInfo Support. Pin
Member 455125623-Jun-10 9:00
Member 455125623-Jun-10 9:00 
QuestionCan't work with 'struct' ? Pin
qmxle1-Oct-09 5:11
qmxle1-Oct-09 5:11 
GeneralExcellent! Accessibility fix possible. Pin
black198122-Jul-09 2:49
black198122-Jul-09 2:49 
QuestionPartial Trust question Pin
Andrey Belykh25-Apr-08 6:57
Andrey Belykh25-Apr-08 6:57 
Generalprivate member supported and easier way with better performance Pin
yzh_x1-Jul-07 19:38
yzh_x1-Jul-07 19:38 
GeneralRe: private member supported and easier way with better performance Pin
Linguar2-Jul-07 9:15
Linguar2-Jul-07 9:15 
GeneralRe: private member supported and easier way with better performance Pin
yzh_x2-Jul-07 16:44
yzh_x2-Jul-07 16:44 
GeneralRe: private member supported and easier way with better performance Pin
Linguar2-Jul-07 18:26
Linguar2-Jul-07 18:26 
You're using methods I don't really use. I never bound the method directly to a specific instance, which is why it required two arguments: one for the instance to refer to on the call, and two, for the parameters to make the call.

The FastMethodInvoker basically emits the object instance (Ldarg_0) and then the series of parameters (Ldarg_1 and then the members of the array are cast into new local values of the proper type, which are then pushed onto the stack.)

After the object and members are pushed to the stack you just call the method with the information provided (Callvirt). You can access private members if, rather than using the module of the type, you use the -DeclaringType- in creating the dynamic method. This associates the dynamic method to the scope of the type in general. So basically both methods have private method access (I stumbled on that by random chance.)

The FastMethodInvoker, by design, literally creates a new object-parameter list method on the fly. Which, as I'm sure you know by looking, there will be a slight hit in speed due to the copying of the parameters into local copies, but that's necessary since you're calling a method using an object array, you have to ensure the values are properly cast/unboxed before making the call.

I use a variant of this system on an inheritance constructor validation system. That is, because constructors, unlike all other (visible) members, do not inherit normally, ensuring derivations of a known design have the constructors needed to instantiate the inherited type(s) is indeed a chore (at a minimum using ugly reflection and checks specific to the individual case.) After verifying the constructor(s) exist(s) I can use this system to create a dynamic method that will instantiate the specific type(s) quickly. The reason I don't just use a simpler system like yours that doesn't emit the duplicate parameter:local listing is the specific signatures are defined through the root-type's constructor attributes. So the signatures would change case-by-case, depending on the specific needs of whoever uses it.

But I guess everyone has their own personal uses for things like this. It's select code that very few people would need.
Edit:
And on top of it all, I added parameter-count checks, primitive up-casting checks (if it requires a double for the parameter, a byte is fine since it's in the range of values, it now casts properly). Something this example lacked.
GeneralWrapper Pin
Midi_Mick7-Jun-07 13:46
professionalMidi_Mick7-Jun-07 13:46 
GeneralRe: Wrapper Pin
johnnycrash9917-May-09 9:03
johnnycrash9917-May-09 9:03 
GeneralComments and structure. Pin
Linguar19-Mar-07 6:56
Linguar19-Mar-07 6:56 
GeneralRe: Comments and structure. Pin
Luyan24-Mar-07 2:35
Luyan24-Mar-07 2:35 
GeneralA Wrapper class for your code Pin
billy p6-Oct-06 9:35
billy p6-Oct-06 9:35 
GeneralI think the result is not exact Pin
happyhippyie3-Sep-06 0:10
happyhippyie3-Sep-06 0:10 
GeneralRe: I think the result is not exact Pin
ueqt20071-Sep-07 19:42
ueqt20071-Sep-07 19:42 
AnswerRe: I think the result is not exact Pin
xx91819-Jan-08 3:29
xx91819-Jan-08 3:29 
QuestionMethodAccessException unhandled when using generic string types Pin
Can Erten9-Aug-06 2:20
Can Erten9-Aug-06 2:20 
QuestionRe: MethodAccessException unhandled when using generic string types Pin
Can Erten9-Aug-06 2:47
Can Erten9-Aug-06 2:47 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.