在 .NET 中使用泛型实现快速后期绑定调用
创建未知类型并调用未知方法,无需 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());
我们的代码中有 knownType
和 knownParamType
,但我们在编译时不知道自动生成的类型。我们也无法定义一个包含 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.MakeGenericMethod
和 Delegate.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>;
}
请注意,有用的泛型委托 Func
和 Action
定义在 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());
我还创建了 F0
和 F1
。您可以以同样的方式添加任何其他需要的函数。
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); };
}
您也可以使用 Get
和 Set
方法访问未知属性。这些方法可通过以下方式获得:
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日:初始发布