通用的快速方法调用器






4.96/5 (73投票s)
2006 年 6 月 27 日
1分钟阅读

276159

3095
方法反射调用很方便,但通常情况下会非常慢。本文描述了一种动态方法调用的替代方法。
引言
有时,我需要动态调用对象的某个方法,而实际方法可能直到运行时才可知。通常,反射很方便,但频繁使用会太慢。本文描述了一种动态方法调用的替代方法。
背景
当我阅读 快速动态属性访问器 这篇文章时,我想到我的项目中有大量的循环反射方法。但这些是方法,而不是属性。但 DynamicMethod
提醒我,也许我可以使用 Emit
生成一个 DynamicMethod
来绑定一个特定的方法,然后再调用它。我希望这能提高性能。
Using the Code
首先,我反射获取将被调用的方法
MethodInfo methodInfo = typeof(Person).GetMethod("Say");
然后,我获取 MethodInvoker
来调用
FastInvokeHandler fastInvoker = GetMethodInvoker(methodInfo);
fastInvoker(new Person(), new object[]{"hello"});
不再使用反射方法,而是像过去一样调用
methodInfo.Invoke(new Person(), new object[]{"hello"});
实现
首先,我需要定义一个委托来适配动态方法
public delegate object FastInvokeHandler(object target,
object[] paramters);
它看起来与 MethodInfo
的 Invoke
方法相同。是的,这意味着我可以使用相同的代码来像过去一样使用它。
这段代码生成 DynamicMethod
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 i = 0; i < paramTypes.Length; i++)
{
paramTypes[i] = ps[i].ParameterType;
}
LocalBuilder[] locals = new LocalBuilder[paramTypes.Length];
for (int i = 0; i < paramTypes.Length; i++)
{
locals[i] = il.DeclareLocal(paramTypes[i]);
}
for (int i = 0; i < paramTypes.Length; i++)
{
il.Emit(OpCodes.Ldarg_1);
EmitFastInt(il, i);
il.Emit(OpCodes.Ldelem_Ref);
EmitCastToReference(il, paramTypes[i]);
il.Emit(OpCodes.Stloc, locals[i]);
}
il.Emit(OpCodes.Ldarg_0);
for (int i = 0; i < paramTypes.Length; i++)
{
il.Emit(OpCodes.Ldloc, locals[i]);
}
il.EmitCall(OpCodes.Call, methodInfo, null);
if (methodInfo.ReturnType == typeof(void))
il.Emit(OpCodes.Ldnull);
else
EmitBoxIfNeeded(il, methodInfo.ReturnType);
il.Emit(OpCodes.Ret);
FastInvokeHandler invoder =
(FastInvokeHandler)dynamicMethod.CreateDelegate(
typeof(FastInvokeHandler));
return invoder;
}
结论
很好,我认为这是一种通用的方法,可以替代大多数反射方法,从而获得大约 50 倍的性能提升。欢迎提出改进建议。
额外优势(MaxGuernsey 提醒):如果在您的代码中发生异常,FastInovker
会抛出原始异常,而 Method.Invoke
会抛出 TargetInvocationException
。
历史
- 2006-7-05:更新以添加静态方法支持。感谢 Manuel Abadia。
- 2006-6-30:更新以添加
ref
/out
参数支持。感谢 Roger 的好建议。