20 February, 2012

.NET Reflection: improved

Every serious .NET developer probably already used .NET reflection mechanism to dynamically invoke methods. Recently I've written plugable framework (using IoC pattern) and I've found out that there is much faster way to invoke methods on different types/assemblies using System.Reflection.Emit.DynamicMethod than regular reflection using Type.InvokeMember(). C# 2.0 (3.5) is required.

The method in different assembly is invoked using delegate. Below is the implementation of static class FastMethodInvoker (we actually generate IL specific code):
  public delegate object FastInvokeHandler(object _target, object[] _params);
 
  /// <summary>
  /// A class to invoke methods (DynamicMethod mechanism).
  /// </summary>
  public static class FastMethodInvoker {
 
    /// <summary>
    /// Invokes method.
    /// </summary>
    /// <param name="_target">Instance of target object.</param>
    /// <param name="_sMethodName">Method name.</param>
    /// <param name="_params">Array of params.</param>
    /// <returns>Result of method.</returns>
    public static object InvokeMethod(object _target, 
                                      string _sMethodName, 
                                      object[] _params) {
      Type type = _target.GetType();
      bool bExists = type.GetMethods().Any(t => t.Name == _sMethodName);
      if (!bExists) 
        throw new ArgumentException(string.Format("Non existing method '{0}' on type '{1}'.", 
                  _sMethodName, 
                  _target.GetType().Name));
      MethodInfo mi = type.GetMethod(_sMethodName);
      FastInvokeHandler fastInvoker = GetMethodInvoker(mi);
      return fastInvoker(_target, _params) ?? null;
    }
 
    /// <summary>
    /// Returns DynamicMethod.
    /// </summary>
    /// <param name="_methodInfo">MethodInfo instance.</param>
    /// <returns>FastInvokeHandler delegate.</returns>
    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 ii = 0; ii < paramTypes.Length; ii++) {
        if (ps[ii].ParameterType.IsByRef)
          paramTypes[ii] = ps[ii].ParameterType.GetElementType();
        else
          paramTypes[ii] = ps[ii].ParameterType;
      }
      LocalBuilder[] locals = new LocalBuilder[paramTypes.Length];
 
      for (int ii = 0; ii < paramTypes.Length; ii++) {
        locals[ii] = il.DeclareLocal(paramTypes[ii], true);
      }
 
      for (int ii = 0; ii < paramTypes.Length; ii++) {
        il.Emit(OpCodes.Ldarg_1);
        EmitFastInt(il, ii);
        il.Emit(OpCodes.Ldelem_Ref);
        EmitCastToReference(il, paramTypes[ii]);
        il.Emit(OpCodes.Stloc, locals[ii]);
      }
      il.Emit(OpCodes.Ldarg_0);
      for (int ii = 0; ii < paramTypes.Length; ii++) {
        if (ps[ii].ParameterType.IsByRef)
          il.Emit(OpCodes.Ldloca_S, locals[ii]);
        else
          il.Emit(OpCodes.Ldloc, locals[ii]);
      }
      il.EmitCall(OpCodes.Callvirt, _methodInfo, null);
      if (_methodInfo.ReturnType == typeof(void))
        il.Emit(OpCodes.Ldnull);
      else
        EmitBoxIfNeeded(il, _methodInfo.ReturnType);
 
      for (int ii = 0; ii < paramTypes.Length; ii++) {
        if (ps[ii].ParameterType.IsByRef) {
          il.Emit(OpCodes.Ldarg_1);
          EmitFastInt(il, ii);
          il.Emit(OpCodes.Ldloc, locals[ii]);
          if (locals[ii].LocalType.IsValueType)
            il.Emit(OpCodes.Box, locals[ii].LocalType);
          il.Emit(OpCodes.Stelem_Ref);
        }
      }
 
      il.Emit(OpCodes.Ret);
      FastInvokeHandler invoker = 
        (FastInvokeHandler)dynamicMethod.CreateDelegate(typeof(FastInvokeHandler));
      return invoker;
    }
 
    /// <summary>
    /// Emits the cast to reference.
    /// </summary>
    /// <param name="il">The il.</param>
    /// <param name="type">The type.</param>
    private static void EmitCastToReference(ILGenerator il, System.Type type) {
      if (type.IsValueType) {
        il.Emit(OpCodes.Unbox_Any, type);
      } else {
        il.Emit(OpCodes.Castclass, type);
      }
    }
 
    /// <summary>
    /// Emits the box if needed.
    /// </summary>
    /// <param name="il">The il.</param>
    /// <param name="type">The type.</param>
    private static void EmitBoxIfNeeded(ILGenerator il, System.Type type) {
      if (type.IsValueType) {
        il.Emit(OpCodes.Box, type);
      }
    }
 
    /// <summary>
    /// Emits the fast int.
    /// </summary>
    /// <param name="il">The il.</param>
    /// <param name="value">The value.</param>
    private static void EmitFastInt(ILGenerator il, int value) {
      switch (value) {
        case -1:
          il.Emit(OpCodes.Ldc_I4_M1);
          return;
        case 0:
          il.Emit(OpCodes.Ldc_I4_0);
          return;
        case 1:
          il.Emit(OpCodes.Ldc_I4_1);
          return;
        case 2:
          il.Emit(OpCodes.Ldc_I4_2);
          return;
        case 3:
          il.Emit(OpCodes.Ldc_I4_3);
          return;
        case 4:
          il.Emit(OpCodes.Ldc_I4_4);
          return;
        case 5:
          il.Emit(OpCodes.Ldc_I4_5);
          return;
        case 6:
          il.Emit(OpCodes.Ldc_I4_6);
          return;
        case 7:
          il.Emit(OpCodes.Ldc_I4_7);
          return;
        case 8:
          il.Emit(OpCodes.Ldc_I4_8);
          return;
      }
 
      if (value > -129 && value < 128) {
        il.Emit(OpCodes.Ldc_I4_S, (SByte)value);
      } else {
        il.Emit(OpCodes.Ldc_I4, value);
      }
    }
  }
 The usage is simple:

a) Invoking some method on external DLL assembly:
...
// invoke method MyMethod(string) on type MyNamespace.MyClass (MyAssembly.dll)
Assembly a = Assembly.LoadFrom("MyAssembly.dll");
object instance = a.CreateInstance("MyNamespace.MyClass");
object[] args = new object[] { "some_string_argument" };
object result = FastMethodInvoker.InvokeMethod(instance, "MyMethod", args);
...

b) Invoking some method on known instance:
... 
// e.g. invoke GetXml() on DataSet instance
Type type = typeof(System.Data.DataSet);
object instance = Activator.CreateInstance(type);
object result = InvokeMethod(instance, "GetXml"null);
...
As a matter of fact this mechanism is about 5x - 10x faster than regular .NET reflection.

5 comments:

de said...

Great Article
C# Training
C# Online Training
C-Sharp Training
Dot Net Training in Chennai
.Net Online Training
ASP.NET Training
ASP NET Training
Core C# Interview Questions
Dot Net Interview Questions

jeeva said...

Great Article… I love to read your articles because your writing style is too good, its is very very helpful for all of us and I never get bored while reading your article because, they are becomes a more and more interesting from the starting lines until the end.

rpa training in bangalore | best rpa training in bangalore
RPA training in bangalore | rpa courses in bangalore

Unknown said...

This is such a good post. One of the best posts that I\'ve read in my whole life. I am so happy that you chose this day to give me this. Please, continue to give me such valuable posts. Cheers!
Python training in bangalore
Python course in pune
Python training in bangalore

rohini said...

Really very nice blog information for this one and more technical skills are improve,i like that kind of post.
AWS Training in Bangalore

Nithya Sri said...
This comment has been removed by the author.