Pages

Thursday, April 29, 2010

C# Dynamic Methods

C# Dynamic Methods


One thing that I love about Net and C# is the CIL, and the things that can be done with it, from the managed code. The platform offers great metadata classes called reflection but the downside of them is that they are very slow, so to overcome this we can build a dynamic method and emit IL op codes, which is much faster than plain reflection, and this is just the beginning what we can do with it, Even more fun thing is the ability to disable JIT checking so we have access to anything in the IL with no consequences like private fields of a class, this makes a possibility to do fast deep cloning. And perhaps the most exciting feature of this is the ability to create whole types in memory, and do code injection, so from within C# an object oriented language we can create an Aspect oriented extensions.

Let's start with dynamic method.

First we need a Delegate for out dynamic method.

/// <summary>
/// Object Invoker Delegate used for dynamic method invocation.
/// </summary>
public delegate object ObjectInvoker(object target, object[] parameters);

The delegate parameters reflect the dynamic method, meaning that we need a target class and parameters to pass to.

Next lets define a base class for our dynamic operations, and put common methods there like for e.g Boxing operation, and Emiting Ints for arrays, and of course our dynamic method building method.

public class DynamicBase
{
    /// <summary>
    /// Creates the method call.
    /// </summary>
    /// <param name="methodInfo">The method info.</param>
    /// <param name="ilGenerator">The il generator.</param>
    protected static void CreateMethodCall(MethodInfo methodInfo, ILGenerator ilGenerator)
    {
        ParameterInfo[] parametersInfo = methodInfo.GetParameters();
        Type[] paramTypes = new Type[parametersInfo.Length];

        for (int i = 0; i < paramTypes.Length; i++)
        {
            if (parametersInfo[i].ParameterType.IsByRef)
            {
                paramTypes[i] = parametersInfo[i].ParameterType.GetElementType();
            }
            else
            {
                paramTypes[i] = parametersInfo[i].ParameterType;
            }
        }

        if (!methodInfo.IsStatic)
        {
            ilGenerator.Emit(OpCodes.Ldarg_0);
        }

        for (int i = 0; i < paramTypes.Length; i++)
        {
            ilGenerator.Emit(OpCodes.Ldarg_1);
            EmitInt(ilGenerator, i);
            ilGenerator.Emit(OpCodes.Ldelem_Ref);
            EmitCast(ilGenerator, paramTypes[i]);
        }

        if (methodInfo.IsStatic)
        {
            ilGenerator.EmitCall(OpCodes.Call, methodInfo, null);
        }
        else
        {
            ilGenerator.EmitCall(OpCodes.Callvirt, methodInfo, null);
        }
        if (methodInfo.ReturnType == typeof(void))
        {
            ilGenerator.Emit(OpCodes.Ldnull);
        }
        else
        {
            ilGenerator.DeclareLocal(methodInfo.ReturnType);
            EmitBoxing(ilGenerator, methodInfo.ReturnType);
            ilGenerator.Emit(OpCodes.Stloc_0);
            ilGenerator.Emit(OpCodes.Ldloc_0);
        }

        ilGenerator.Emit(OpCodes.Ret);
    }

    /// <summary>
    /// Emits the Cast.
    /// </summary>
    /// <param name="ilGenerator">the il Generator.</param>
    /// <param name="type">the type.</param>
    protected static void EmitCast(ILGenerator ilGenerator, Type type)
    {
        if (type.IsValueType)
        {
            ilGenerator.Emit(OpCodes.Unbox_Any, type);
        }
        else
        {
            ilGenerator.Emit(OpCodes.Castclass, type);
        }
    }

    /// <summary>
    /// Emits the Boxing.
    /// </summary>
    /// <param name="ilGenerator">the il Generator.</param>
    /// <param name="type">the type.</param>
    protected static void EmitBoxing(ILGenerator ilGenerator, Type type)
    {
        if (type.IsValueType)
        {
            ilGenerator.Emit(OpCodes.Box, type);
        }
    }

    /// <summary>
    /// Emits the Int.
    /// </summary>
    /// <param name="ilGenerator">the il Generator.</param>
    /// <param name="value">the value.</param>
    protected static void EmitInt(ILGenerator ilGenerator, int value)
    {
        if (value > -129 && value < 128)
        {
            ilGenerator.Emit(OpCodes.Ldc_I4_S, (SByte)value);
        }
        else
        {
            ilGenerator.Emit(OpCodes.Ldc_I4, value);
        }
    }
}

Ok this is a lot of code so we need some explanation, The "Create Method Call" is the main method that basically wraps itself around a method and call is with the specified parameters, and thats all that is to it.

From the code side it looks like this:

public int Dynamic(object[] args)
{
    return Test((int)args[0], (int)args[1]);
}

public int Test(int a, int b)
{
    return a + b;
}

Note: that this is very similar to how the new dynamic key word work except MS is probably creating proxy types on the references that you assign.

Now that we have out base class we need to define a concrete class that uses the "Create Caller Code" method, so before that let's define a class diagram (Its kinda not needed as the example is simple but let's do it anyways).



We also need to implement some basic caching as we usually only need to create the body of a dynamic method only once, and this takes up time so it is advised to do that before application starts for e.g, or when you don't care about the performance on first start.

/// <summary>
/// The Dynamic Method class.
/// </summary>
public class DynamicMethod : DynamicBase
{
    private static readonly HybridDictionary dynamicMethodCache = new HybridDictionary();

    /// <summary>
    /// Clears the Dynamic Cache.
    /// </summary>
    public static void ClearDynamicCache()
    {
        dynamicMethodCache.Clear();
    }

    /// <summary>
    /// Calls the Method dynamicly.
    /// </summary>
    /// <param name="methodInfo">the method Info.</param>
    public static ObjectInvoker CallMethod(MethodInfo methodInfo)
    {

        if (dynamicMethodCache[methodInfo] == null)
        {
            System.Reflection.Emit.DynamicMethod dynamicMethod = 
                new System.Reflection.Emit.DynamicMethod(string.Empty, typeof(object)
                    , new Type[] { typeof(object), typeof(object[]) }, methodInfo.DeclaringType.Module);

            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
            CreateMethodCall(methodInfo, ilGenerator);

            ObjectInvoker invoker = (ObjectInvoker)dynamicMethod.CreateDelegate(typeof(ObjectInvoker));

            lock (dynamicMethodCache)
            {
                dynamicMethodCache[methodInfo] = invoker;
            }

            return invoker;
        }
        else
        {
            return (ObjectInvoker)dynamicMethodCache[methodInfo];
        }

    }
}

And now we have a dynamic method, that's ready to be used.

So now let's take our test method and try to use the dynamic method we just created.

int dynamicResult = (int)DynamicMethod
    .CallMethod(a.GetType().GetMethod("Test"))(a, new object[] { 2, 3 });

The result of the operation is 5. When calling the method 10 thousand times and more you will see that the performance is better when comparing to a reflection invoke method, so this makes it worthy to use in scenarios like ORMs for e.g.

This concludes this part of the article, but there is a lot more to it then just some simple method calling, the most interesting part of dynamic method is the JIT check, so cloning is possible and some other scenarios as well, another cool thing that you can do is to swamp methods in memory:

IntPtr methodPointer = a.GetType().GetMethod("Test").MethodHandle.Value;

But the value is read only for safety reasons, but you can convert it to unmanaged pointer and there you go, if you know how the CLR memory is mapped (and you can know by reading ECMA or blogging), you can swamp method Test for say your dynamic method, but this is evil so i'm not gonna cover it, but keep in mind that you can do magic thing with dynamic code.

No comments:

 
ranktrackr.net