65.9K
CodeProject 正在变化。 阅读更多。
Home

在 .NET 中使用泛型实现快速后期绑定调用

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (14投票s)

2009年2月27日

Ms-PL

4分钟阅读

viewsIcon

40190

downloadIcon

140

创建未知类型并调用未知方法,无需 IL 生成和 Invoke 调用。

前言

想象一下,您正在使用一个动态创建的库。想象一下,您正在为您的应用程序构建一个插件引擎。在这两种情况下,您都将动态加载程序集。然后,您需要创建来自已加载程序集中的一些未知类的实例。也许您甚至需要调用这些未知类中的一些未知函数。您可以通过使用基类和接口来解决其中的一部分问题。然而,在某些情况下,仍然需要使用反射。不幸的是,众所周知,.NET 中的反射速度非常慢。

我可以举一个这种情况的例子

//autogenerated code
namespace plugin {
    public class l5fks9rffn33md : knownParamType
    {
    }

    public class p4sdfm2mlwd5 : knownType
    {
        public void testAction(l5fks9rffn33md p)
        { 
        }
    }
}

我们需要在代码中这样做

//new plugin.p4sdfm2mlwd5().testAction(new plugin.l5fks9rffn33md());

我们的代码中有 knownTypeknownParamType,但我们在编译时不知道自动生成的类型。我们也无法定义一个包含 testAction 的接口,因为它在签名中使用了未知类型。

可以通过以下方式完成必要的操作:

Type unknownType = Type.GetType("plugin.p4sdfm2mlwd5");
Type unknownParamType = Type.GetType("plugin.l5fks9rffn33md");

knownType instance = (knownType)Activator.CreateInstance(unknownType);
knownParamType param = (knownParamType)Activator.CreateInstance(unknownParamType);
unknownType.GetMethod("testAction").Invoke(instance, new object[] {param});

然而,这段代码速度非常慢(比直接调用慢 100 倍)。此外,它也不是类型安全的。

还有另一种方法:动态方法,它使用 Reflection.Emit 进行动态 IL 生成。这种方法非常复杂(但也非常强大)。C# 4.0 提供了对未知方法的原生调用,但尚未发布。我想介绍一种使用泛型强大功能并以与 IL 生成相同的速度完成相同事情的方法。它不像 MethodInfo.Invoke 那么简单,但也不像 Reflection.Emit 那么复杂。

实例创建

我们想创建一个带有无参数构造函数的类型的实例。让我们创建一个通用的函数来完成它

public static object createNewObj<T>() where T : new()
{
      return new T();
}

这很简单,不是吗?

但是如何调用这个函数?我们不能写

//Helper.createNewObj<plugin.p4sdfm2mlwd5>();

因为类型 p4sdfm2mlwd5 是未知的。

让我们使用两个不常用的函数:MethodInfo.MakeGenericMethodDelegate.CreateDelegate

public static Func<object> MakeNewDlg(Type t)
{
	return Delegate.CreateDelegate(typeof(Func<object>), 
		typeof(Helper).GetMethod("createNewObj").MakeGenericMethod(new Type[]
		{ t })) as Func<object>;
}

请注意,有用的泛型委托 FuncAction 定义在 System 命名空间中。现在我们可以调用

Func<object> knownTypeCreator = 
	Helper.MakeNewDlg(Type.GetType("plugin.p4sdfm2mlwd5"));
knownType instance = (knownType)knownTypeCreator();

最好也将 knownTypeCreator 存储在某处,并在每次需要新的 p4sdfm2mlwd5 实例时使用它。它将运行得很快,因为它只是一个 delegate 并且不使用反射。

然而,这段代码仍然不是类型安全的。下面的代码更好

public static Func<K> MakeNewDlg<K>(Type t)
{
	return Delegate.CreateDelegate(typeof(Func<K>),    
	    typeof(Helper).GetMethod("createNewObj2").MakeGenericMethod(new Type[]
	    { t, typeof(K) })) as Func<K>;
}

public static K createNewObj2<T, K>() where T : K, new()
{
	return new T();
}

它返回一个类型化的 delegate,我们可以省略类型转换

Func<knownType> knownTypeCreator = Helper.MakeNewDlg <knownType>
				(Type.GetType("plugin.p4sdfm2mlwd5"));
knownType instance = knownTypeCreator();

总结:我们有各种生成器,可以创建任意数量的实例。下一步是调用方法。MakeGenericMethod 将再次帮助我们。

方法调用

Delegate.CreateDelegate 函数允许为非静态方法创建 static 委托。这是重复使用相同的 delegate 与不同对象实例的绝佳能力,如下所示:

class X
{
    void test() { }
}
…
Action<X> caller = (Action<X>)Delegate.CreateDelegate(typeof(Action<X>), 
	typeof(X).GetMethod("test"));
X a = new X();
X b = new X();
caller(a);
caller(b);

看起来,我们可以用同样的方式达到我们的目的

MethodInfo method = Type.GetType("p4sdfm2mlwd5").GetMethod("testAction");
Action<knownType, knownParamType> d = 
	(Action<knownType, knownParamType>)Delegate.CreateDelegate
	(typeof(Action<knownType, knownParamType>), method);

哦!这段代码会生成运行时错误!发生这种情况是因为新创建的 delegate 应该与底层方法具有相同的签名(实际上,它可以返回基类型作为结果类型,但不能使用基类型作为参数,有关详细信息,请参阅MSDN)。此外,目标类型应该具有该方法。

我们可以通过匿名方法来解决这个问题

Action<p4sdfm2mlwd5, l5fks9rffn33md> tmp = 
	(Action<p4sdfm2mlwd5, l5fks9rffn33md>)Delegate.CreateDelegate
	(typeof(Action<p4sdfm2mlwd5, l5fks9rffn33md>), method);
Action<knownType, knownParamType> d = delegate
	(knownType target, knownParamType p) { tmp((p4sdfm2mlwd5)target, 
	(l5fks9rffn33md)p); };

现在 CreateDelegate 没有问题了,但有另一个问题。这段代码无法编译,因为它使用了未知类型。

让我们将其包装在一个消除不良类型的泛型函数中。

public static Action<knownT, knownP1> A1<T, P1, knownT, knownP1>(MethodInfo method)
        where T : knownT
        where P1 : knownP1
{
        // create first delegate. It is not fine because 
        // its signature contains unknown types T and P1
        Action<T, P1> d = (Action<T, P1>)Delegate.CreateDelegate
				(typeof(Action<T, P1>), method);
        // create another delegate having necessary signature. 
        // It encapsulates first delegate with a closure
        return delegate(knownT target, knownP1 p) { d((T)target, (P1)p); };
}

请注意,我将函数命名为“A1”。当我们按名称搜索它时,它将具有特殊含义。

还剩下最后一件事:我们应该以与 createNewObj 相同的方式调用这个漂亮的函数(还记得吗?)。调用代码更复杂,因为它应该支持不同的参数数量。

/// <summary>
/// Creates static caller delegate for specified method
/// </summary>
/// <typeparam name="T">signature of the delegate</typeparam>
/// <param name="method">method to surround</param>
/// <returns>caller delegate with specified signature</returns>
public static T MakeCallDlg<T>(MethodInfo method) 
{            
	// we're going to select necessary generic function and 
         // parameterize it with specified signature

	// 1. select function name accordingly to parameters count
	string creatorName = (method.ReturnParameter.ParameterType == 
	typeof(void) ? "A" : "F") + method.GetParameters().Length.ToString();

	// 2. create parameterization signature
	List<Type> signature = new List<Type>();
	// first type parameter is type of target object
	signature.Add(method.DeclaringType);

	//next parameters are real types of method arguments
	foreach (ParameterInfo pi in method.GetParameters())
	{
	    signature.Add(pi.ParameterType);
	}

	// last parameters are known types of method arguments
	signature.AddRange(typeof(T).GetGenericArguments());

	// 3. call generator function with Delegate.Invoke. 
	// We can do it because the generator will be called only once. 
	// Result will be cached somewhere then.
	return (T)typeof(Helper).GetMethod(creatorName).MakeGenericMethod
		(signature.ToArray()).Invoke(null, new object[] { method });
}

您应该准备几个函数,例如 A0, A1, A2 等,F0, F1, F2 等,用于您需要的任何参数数量。MakeCallDlg 将选择必要的通用函数并用泛型参数填充它。

现在我们可以调用 testAction

Type unknownType = Type.GetType("plugin.p4sdfm2mlwd5");
Type unknownParamType = Type.GetType("plugin.l5fks9rffn33md");
MethodInfo test = unknownType.GetMethod("testAction");

Func<knownType> unknownTypeCreator = 
	Helper.MakeNewDlg<knownType>(unknownType);
Func<knownParamType> unknownParamTypeCreator = 
	Helper.MakeNewDlg<knownParamType>(unknownParamType);            
Action<knownType, knownParamType> methodCaller = 
	Helper.MakeCallDlg<Action<knownType, knownParamType>>(test);

knownType instance = unknownTypeCreator();
knownParamType param = unknownParamTypeCreator();
methodCaller(instance, param);

一次又一次……

methodCaller(unknownTypeCreator(),unknownParamTypeCreator());
methodCaller(unknownTypeCreator(),unknownParamTypeCreator());
methodCaller(unknownTypeCreator(),unknownParamTypeCreator());

我还创建了 F0F1。您可以以同样的方式添加任何其他需要的函数。

public static Func<knownT, knownR> F0<T, knownT, knownR>(MethodInfo method)
            where T : knownT
{
	Func<T, knownR> d = (Func<T, knownR>)Delegate.CreateDelegate
					(typeof(Func<T, knownR>), method);
	return delegate(knownT target) { return d((T)target); };
}

public static Func<knownT, knownP1, knownR> F1<T, P1, knownT, knownP1, knownR>
							(MethodInfo method)
            where T : knownT
            where P1 : knownP1
{
	Func<T, P1, knownR> d = (Func<T, P1, knownR>)Delegate.CreateDelegate
					(typeof(Func<T, P1, knownR>), method);
	return delegate(knownT target, knownP1 p) { return d((T)target, (P1)p); };
}

您也可以使用 GetSet 方法访问未知属性。这些方法可通过以下方式获得:

unknownType.GetProperty("testProperty").GetGetMethod();
unknownType.GetProperty("testProperty").GetSetMethod();

结论

我比较了不同调用类型的性能(请参阅下面的基准代码): 

  • 直接调用 - 2200
  • 我的方式 - 7400
  • MethodInfo.Invoke- 192000

这是示例的完整源代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

   #region autogenerated code
    
    //autogenerated code
    namespace plugin {
        public class l5fks9rffn33md : knownParamType
        {
        }

        public class p4sdfm2mlwd5 : knownType
        {
            private l5fks9rffn33md field;

            public void testAction(l5fks9rffn33md p)
            {
                field = p;
            }
            public l5fks9rffn33md testFunction(l5fks9rffn33md p)
            {
                return p;
            }
        }
    } 
    #endregion

    #region known types
    public class knownParamType
    {
    }

    public class knownType
    {
    } 
    #endregion       

    class Helper
    {
        private static Action<knownT, knownP1> A1<T, P1, knownT, 
					knownP1>(MethodInfo method)
            where T : knownT
            where P1 : knownP1
        {
            // create first delegate. It is not fine because its 
	   // signature contains unknown types T and P1
            Action<T, P1> d = (Action<T, P1>)Delegate.CreateDelegate
					(typeof(Action<T, P1>), method);
            // create another delegate having necessary signature. 
	   // It encapsulates first delegate with a closure
            return delegate(knownT target, knownP1 p) { d((T)target, (P1)p); };
        }

        private static Func<knownT, knownR> F0<T, knownT, knownR>(MethodInfo method)
            where T : knownT
        {
            Func<T, knownR> d = (Func<T, knownR>)Delegate.CreateDelegate
					(typeof(Func<T, knownR>), method);
            return delegate(knownT target) { return d((T)target); };
        }

        private static Func<knownT, knownP1, knownR> 
		F1<T, P1, knownT, knownP1, knownR>(MethodInfo method)
            where T : knownT
            where P1 : knownP1
        {
            Func<T, P1, knownR> d = (Func<T, P1, knownR>)
		Delegate.CreateDelegate(typeof(Func<T, P1, knownR>), method);
            return delegate(knownT target, knownP1 p) { return d((T)target, (P1)p); };
        }

        /// <summary>
        /// Creates static caller delegate for specified method
        /// </summary>
        /// <typeparam name="T">signature of the delegate</typeparam>
        /// <param name="method">method to surround</param>
        /// <returns>caller delegate with specified signature</returns>
        public static T MakeCallDlg<T>(MethodInfo method) 
        {            
            // we're going to select necessary generic function 
	   // and parameterize it with specified signature

            // 1. select function name accordingly to parameters count
            string creatorName = (method.ReturnParameter.ParameterType == 
		typeof(void) ? "A" : "F") + 
		method.GetParameters().Length.ToString();

            // 2. create parametrization signature
            List<Type> signature = new List<Type>();
            // first type parameter is type of target object
            signature.Add(method.DeclaringType);

            //next parameters are real types of method arguments
            foreach (ParameterInfo pi in method.GetParameters())
            {
                signature.Add(pi.ParameterType);
            }

            // last parameters are known types of method arguments
            signature.AddRange(typeof(T).GetGenericArguments());

            // 3. call generator function with Delegate.Invoke. 
	   // We can do it because the generator will be called only once. 
	   // Result will be cached somewhere then.
            return (T)typeof(Helper).GetMethod(creatorName, 
		BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod
		(signature.ToArray()).Invoke(null, new object[] { method });
        }


        /// <summary>
        /// Creates static creator delegate for specified type
        /// </summary>
        /// <typeparam name="K">known parent type of specified type</typeparam>
        /// <param name="t">specified type to create</param>
        /// <returns>delegate</returns>
        public static Func<K> MakeNewDlg<K>(Type t)
        {
            return Delegate.CreateDelegate(typeof(Func<K>), 
		typeof(Helper).GetMethod("createNewObj2", 
		BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod
		(new Type[] { t, typeof(K) })) as Func<K>;
        }

        private static K createNewObj2<T, K>() where T : K, new()
        {
            return new T();
        }

        /// <summary>
        /// Creates static creator delegate for specified type
        /// </summary>        
        /// <param name="t">specified type to create</param>
        /// <returns>delegate</returns>
        public static Func<object> MakeNewDlg(Type t)
        {
            return Delegate.CreateDelegate(typeof(Func<object>), 
		typeof(Helper).GetMethod("createNewObj").MakeGenericMethod
		(new Type[] { t })) as Func<object>;
        }

        private static object createNewObj<T>() where T : new()
        {
            return new T();
        }
    }    
    
    class Program
    {
        static void Main(string[] args)
        {
            //find unknown types and method by names
            Type unknownType = Type.GetType("plugin.p4sdfm2mlwd5");
            Type unknownParamType = Type.GetType("plugin.l5fks9rffn33md");
            MethodInfo test = unknownType.GetMethod("testFunction");

            //create and store delegates
            Func<knownType> unknownTypeCreator = 
		Helper.MakeNewDlg<knownType>(unknownType);
            Func<knownParamType> unknownParamTypeCreator = 
		Helper.MakeNewDlg<knownParamType>(unknownParamType);
            Func<knownType, knownParamType, knownParamType> methodCaller = 
		Helper.MakeCallDlg<Func<knownType, knownParamType, 
		knownParamType>>(test);

            //call delegates
            knownType instance = unknownTypeCreator();
            knownParamType param = unknownParamTypeCreator();
            knownParamType result = methodCaller(instance, param);

            //benchmark
            int times = 50000000;

            #region direct call 
            int i = times;
            plugin.p4sdfm2mlwd5 unknownInstance = new plugin.p4sdfm2mlwd5();
            plugin.l5fks9rffn33md unknownParam = new plugin.l5fks9rffn33md();
            DateTime start = DateTime.Now;

            while (i-- > 0)
                result = unknownInstance.testFunction(unknownParam);

            Console.WriteLine("Direct call - {0}", 
		(DateTime.Now - start).TotalMilliseconds);  
            #endregion

            #region my way
            i = times;
            start = DateTime.Now;

            while (i-- > 0)
                result = methodCaller(instance, param);

            Console.WriteLine("My way - {0}", 
		(DateTime.Now - start).TotalMilliseconds); 
            #endregion

            #region Invoke
            i = times;
            object[] paramArray = new object[] { param };
            start = DateTime.Now;

            while (i-- > 0)
                 result = (knownParamType)test.Invoke(instance, paramArray);

            Console.WriteLine("Invoke - {0}", (DateTime.Now - start).TotalMilliseconds); 
            #endregion

            Console.ReadKey();
        }
    }

历史

  • 2009年2月27日:初始发布
© . All rights reserved.