Reflection.Emit的性能惩罚
本文关键字:惩罚 性能 Emit Reflection | 更新日期: 2023-09-27 18:19:59
我正在考虑一个可能的解决方案来解决我正在处理的问题(.NET 3.5 WinForms应用程序)。
在我们的应用程序中,我们有许多方法(C#),其参数由应用程序的用户输入。
例如:
public void DoSomething(string name, DateTime date)
{
// ...
}
其中name,date当前使用一个简单的文本框输入。我们希望拥有丰富的编辑器、受密码保护的输入框、自动完成等功能。
我希望用户的输入使用PropertyGrid,但是此控件只能绑定到对象,而不能绑定到参数。
我已经阅读了MSDN杂志上关于ProparyGrid:的两篇优秀文章
ICustomTypeDescriptor,第1部分
ICustomTypeDescriptor,第2部分
然而,在将绑定到PropertyGrid的对象事先已知的情况下,这似乎很有帮助,而我的情况并非如此。
可以支持这种情况吗?有没有简单易用的解决方案?
我想在运行时使用Reflection.Emit创建一个"temp"对象,其属性将是方法的参数。我以前从未这样做过(使用Reflection.Emit命名空间),我想知道使用它会带来什么性能损失?(它是否真的在运行时在内存中编译代码,或者它是如何工作的?)
是的,可以使用Reflection.Emit执行此操作(创建一个具有与方法参数对应的属性的代理类型)。完成此操作后,您可以将代理对象的实例分配给PropertyGrid,然后使用输入的值来调用该方法。然而,你想做的并不是小事。
我将向您介绍使用Reflection.Emit.创建类型的MSDN TypeBuilder文档
为了回答您关于性能的问题,是的,代码是在"内存"中编译的。您通常想要做的是将生成的类型缓存在字典中,以便可以重用。最大的性能冲击是Type生成。创建该类型的实例可以变得非常便宜(取决于您的操作方式-Activator.CreateInstance()是最慢的,类似于以下内容:
private Func<T> GetCreator()
{
if (_Creator == null)
{
Expression body;
var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
var defaultConstructor = typeof(T).GetConstructor(bindingFlags, null, new Type[0], null);
if (defaultConstructor != null)
{
// lambdaExpression = () => (object) new TClass()
body = Expression.New(defaultConstructor);
}
else
{
// lambdaExpression = () => FormatterServices.GetUninitializedObject(classType)
var getUnitializedObjectMethodInfo = typeof(FormatterServices).GetMethod("GetUninitializedObject", BindingFlags.Public | BindingFlags.Static);
body = Expression.Call(getUnitializedObjectMethodInfo, Expression.Constant(typeof(T)));
}
var lambdaExpression = Expression.Lambda<Func<T>>(body);
_Creator = lambdaExpression.Compile();
}
return _Creator;
}
它允许您通过简单地调用来创建一个新实例
object obj = GetCreator()();
使用这种模式,当您的应用程序刚刚启动时,您会看到性能下降,但随着缓存未命中的减少,它的性能几乎与内联代码一样好。
您可以使用类似的方法来生成Invoker——这里有一个很好的例子:
http://www.codeproject.com/KB/cs/FastMethodInvoker.aspx
这里或多或少有相同的问题及其解决方案。它是为.NET 3.5编写的,运行良好。目标是将所有web方法集中在一个web服务(.asmx)中,并从一个位置调用任何注册的方法。代码可能要小得多。但是,由于一些强制转换,它有点长。
public object ExecuteMethod(string moduleName, string methodName, object[] arguments)
{
CmsModuleMethodInfo methodInfo = CmsModuleManager.GetModuleMethod(moduleName, methodName);
...
ParameterInfo[] paramInfo = methodInfo.Method.GetParameters();
Object[] parameters = new Object[paramInfo.Length];
Type[] paramTypes = paramInfo.Select(x => x.ParameterType).ToArray();
for (int i = 0; i < parameters.Length; ++i)
{
Type paramType = paramTypes[i];
Type passedType = (arguments[i] != null) ? arguments[i].GetType() : null;
if (paramType.IsArray)
{
// Workaround for InvokeMethod which is very strict about arguments.
// For example, "int[]" is casted as System.Object[] and
// InvokeMethod raises an exception in this case.
// So, convert any object which is an Array actually to a real Array.
int n = ((Array)arguments[i]).Length;
parameters[i] = Array.CreateInstance(paramType.GetElementType(), n);
Array.Copy((Array)arguments[i], (Array)parameters[i], n);
}
else if ((passedType == typeof(System.Int32)) && (paramType.IsEnum))
{
parameters[i] = Enum.ToObject(paramType, (System.Int32)arguments[i]);
}
else
{
// just pass it as it's
parameters[i] = Convert.ChangeType(arguments[i], paramType);
}
}
object result = null;
try
{
result = methodInfo.Method.Invoke(null, parameters);
}
catch (TargetInvocationException e)
{
if (e.InnerException != null)
{
throw e.InnerException;
}
}
return result;
}