Friday, May 08, 2009

Strong Typed, High Performance Reflection with C# Delegate

Update: Open source project SharpCut delivers a less than 50K library which does what described here in one line plus much more. Check it out.

Content

  1. Inspiration: C#.Net Calling Grandparent's Virtual Method (base.base in C#)
  2. Prototype: Strong Typed, High Performance Reflection with C# Delegate (Part I) <= you are here
  3. Performance: Strong Typed, High Performance Reflection with C# Delegate (Part II)
  4. Library Usage: Strong Typed, High Performance Reflection with C# Delegate (Part III) 

The process of finding a solution for base.base.VirtualMethod() in C# inspired me to create utility/extension method for reflection. The goal was to use delegate instead of MethodInfo.Invoke or DynamicMethod.Invoke. Using the Invoke method requires boxing all value type to object, create an object array, unboxing before call the actual method, boxing/unboxing the return value if necessary and cast the return value to the excepted data type. Very involved indeed.

If the MethodInfo or DynamicMethod is reused to make the call, using Invoke method is costly and error prone. Can we create a Delegate object out of them so that it can be strong typed, and hopefully more efficient.

DynamicMethod class has overloaded methods named CreateDelegate. And Delegate class too has CreateDelegate methods that takes MethodInfo, so .Net framework does have the weapon we need.

Let's go step by step.

Utility method to create non-virtual invoke DynamicMethod from MethodInfo

Fist, extract the part that create the DynamicMethod in my last post and enhance it to work with any given MethodInfo object.

        public static DynamicMethod CreateNonVirtualDynamicMethod(this MethodInfo method)
        {
            int offset = (method.IsStatic ? 0 : 1);
            var parameters = method.GetParameters();
            int size = parameters.Length + offset;
            Type[] types = new Type[size];
            if (offset > 0) types[0] = method.DeclaringType;
            for (int i = offset; i < size; i++)
            {
                types[i] = parameters[i - offset].ParameterType;
            }

            DynamicMethod dynamicMethod = new DynamicMethod(
                "NonVirtualInvoker_" + method.Name, method.ReturnType, types, method.DeclaringType);
            ILGenerator il = dynamicMethod.GetILGenerator();
            for (int i = 0; i < types.Length; i++) il.Emit(OpCodes.Ldarg, i);
            il.EmitCall(OpCodes.Call, method, null);
            il.Emit(OpCodes.Ret);
            return dynamicMethod;
        }

With this tool, we can slim down our implementation of base.base in class C quite a bit.


    class C : B
    {
        private static readonly DynamicMethod baseBaseFoo;
        static C()
        {
            MethodInfo fooA = typeof(A).GetMethod("foo", BindingFlags.Public | BindingFlags.Instance);
            baseBaseFoo = fooA.CreateNonVirtualDynamicMethod();
        }
        public override string foo() { return (string)baseBaseFoo.Invoke(null, new object[] { this }); }
    }

Create Delegate from DynamicMethod

As we said in the beginning that the DynamicMethod.Invoke is verbose and and inefficient. The solution is to create a Delegate out of DynamicMethod and use the Delegate.  We can do it right inside the class C, and you can see that the call to the baseBaseFoo now is short, clean and strong typed. We'll discuss the performance benefit in next post.


    class C : B
    {
        private static readonly Func<A, string> baseBaseFoo;
        static C()
        {
            MethodInfo fooA = typeof(A).GetMethod("foo", BindingFlags.Public | BindingFlags.Instance);
            baseBaseFoo = 
                (Func<A, string>)fooA.CreateNonVirtualDynamicMethod()
                .CreateDelegate(typeof(Func<A, string>));
        }
        public override string foo() { return baseBaseFoo(this); }
    }

This is great with one downside is that we had to cast here and there when we create the Delegate. Can we extract this logic into a generic method so that in class C I can simply do this?

    baseBaseFoo = GetNonVirtualInvoker<Func<A, string>>(fooA);

My first attempt was not successful. See the code below


        public static TDelegate GetNonVirtualInvoker<TDelegate>(this MethodInfo method)
            where TDelegate : Delegate
        {
            var dynamicMethod = CreateNonVirtualDynamicMethod(method);
            return (TDelegate)dynamicMethod.CreateDelegate(typeof(TDelegate));
        }

It would be most ideal if this works so that the generic method only takes Delegate as type parameter. But the compiler give me a red line under the constrain type Delegate and complained that "Constraint cannot be special class 'System.Delegate'". Why Microsoft? Come and vote for the change here!

My second attempt is to use cast. But the compiler was still unhappy with error message: "Cannot cast expression of type 'System.Delegate' to 'TDelegate'".

        public static TDelegate GetNonVirtualInvoker<TDelegate>(this MethodInfo method)
        {
            var dynamicMethod = CreateNonVirtualDynamicMethod(method);
            return (TDelegate)dynamicMethod.CreateDelegate(typeof(TDelegate));
        }

This is very annoying. Is something wrong with the framework/language design? The workaround turn out to be very simply. Cast to object then back to TDelegate. (Update 5/15: actually there is a workaround to avoid double cast)

Finally the code below works:
        public static TDelegate GetNonVirtualInvoker<TDelegate>(this MethodInfo method)
        {
            var dynamicMethod = CreateNonVirtualDynamicMethod(method);
            return (TDelegate)(object)dynamicMethod.CreateDelegate(typeof(TDelegate));
        }

Thus the class C can be further simplified to:

    class C : B
    {
        private static readonly Func<A, string> baseBaseFoo;
        static C()
        {
            MethodInfo fooA = typeof(A).GetMethod("foo", BindingFlags.Public | BindingFlags.Instance);
            baseBaseFoo = fooA.GetNonVirtualInvoker<Func<A, string>>();
        }
        public override string foo() { return baseBaseFoo(this); }
    }

One stop shop extension method returns Delegate from type and method name

Now look at the goal set in the last post and repeated below. The class C must be further cut down to achieve the goal.

    class C : B
    {
        private static readonly Func<A, string> baseBaseFoo = 
            typeof(A).GetNonVirtualInvoker<Func<A, string>>("foo");
        public override string foo() { return baseBaseFoo(this); }
    }

Indeed, getting the MethodInfo object from a given type is never a one liner, it becomes verbose when method is overloaded thus parameters types matching is necessary. Things are getting more interesting now. Delegate has the precise information about the signature of method. Our utility method can be further enhanced to find the method from a given type with just a method name, because the parameter information can be found in the Delegate type.

        public static TDelegate GetNonVirtualMethod<TDelegate>(this Type type, string name)
        {
            Type delegateType = typeof(TDelegate);
            if (!typeof(MulticastDelegate).IsAssignableFrom(delegateType))
            {
                throw new InvalidOperationException(
                    "Expecting type parameter to be a Delegate type, but got " +
                    delegateType.FullName);
            }
            var invoke = delegateType.GetMethod("Invoke");
            ParameterInfo[] parameters = invoke.GetParameters();
            int size = parameters.Length - 1;
            Type[] types = new Type[size];
            for (int i = 0; i < size; i++)
            {
                types[i] = parameters[i + 1].ParameterType;
            }
            var method = type.GetMethod(name, 
                BindingFlags.Public | BindingFlags.NonPublic | 
                BindingFlags.Instance | BindingFlags.InvokeMethod, 
                null, types, null);
            if (method == null) return default(TDelegate);
            var dynamicMethod = CreateNonVirtualDynamicMethod(method);
            return (TDelegate)(object)dynamicMethod.CreateDelegate(delegateType);
        }

This extension method let you created a Delegate that can make non-virtual invocation to the named method of given type. The method parameters matches the signature of the Delegate. For example, instance method ClassA.Method1(string, int) matches Delegate(ClassA, string, int). The extension method started with making sure the type parameter is indeed a Delegate type, then retrieve the parameter types from the Delegate's Invoke method, then lookup the method in the given type, create dynamic method and finally create the Delegate.

Continue...

The complete code used in this blog can be found here. The code is the result of inception and prototype of the Common.Reflection. In next few posts, we'll implement formal extension methods with enhanced features and compare the performance between direct method call, reflection invoke and Delegate call.

No comments:

Post a Comment