Reflection.Emit 的性能损失

发布于 2024-12-26 18:56:34 字数 836 浏览 1 评论 0原文

我正在考虑对我正在处理的问题(.NET 3.5 WinForms 应用程序)的可能解决方案。

在我们的应用程序中,我们有许多方法 (C#),其参数由应用程序的用户输入。

一个示例如下:

public void DoSomething(string name, DateTime date)
{
   // ...
}

当前使用简单的文本框输入名称、日期。 我们希望拥有丰富的编辑器、受密码保护的输入框、自动完成等功能。

我希望使用 PropertyGrid 进行用户输入,但是此控件只能绑定到对象,而不能绑定到参数。

我读过 MSDN 杂志上有关 ProperyGrid 的两篇优秀文章:

ICustomTypeDescriptor,第 1 部分

ICustomTypeDescriptor,第 2 部分

然而,这似乎在预先知道将绑定到 PropertyGrid 的对象的情况下很有帮助,但我的情况不是这样。

这个场景可以支持吗?有没有简单易行的解决方案?

我想到使用 Reflection.Emit 在运行时创建一个“临时”对象,其属性将是方法的参数。 我以前从未这样做过(使用 Reflection.Emit 命名空间),我想知道使用它的性能损失? (它实际上在运行时在内存中编译代码吗?或者它是如何工作的?)

I am considering a possible solution to an issue i'm working on (.NET 3.5 WinForms application).

In our app, we have many methods (C#) whose arguments are entered by the user of the application.

An example would be something like:

public void DoSomething(string name, DateTime date)
{
   // ...
}

Where name, date is currently entered using a simple textbox.
We would like to have the benefits of rich editors, password protected input boxes, autocomplete, and more.

I would like the user's input to be made using a PropertyGrid, however this control can only bind to an object, and not arguments.

I have read both excellent articles from MSDN magazine regarding the ProperyGrid:

ICustomTypeDescriptor, Part 1

ICustomTypeDescriptor, Part 2

However this seems to be helpful in a scenario where the object that will be bound to the PropertyGrid is known in advance, which is not my case.

Can this scenario be supported? Is there any simple and easy to implement solution?

I thought of using Reflection.Emit to create a "temp" object at runtime, whose properties would be the method's arguments.
I have never done this before (use of Reflection.Emit namespace) and i would like to know the performance penalty of using it? (does it actually compile the code at runtime in-memory or how does it work?)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

会傲 2025-01-02 18:56:34

是的,可以使用 Reflection.Emit 来执行此操作(创建具有与方法参数对应的属性的代理类型)。一旦有了这个,您就可以将代理对象的实例分配给 PropertyGrid,然后使用输入的值来调用该方法。然而,您想做的事情并不是微不足道的。

我会向您指出 TypeBuilder 的 MSDN 文档,以获取使用 Reflection.Emit 创建类型的示例。

为了回答您有关性能的问题,是的,代码是在“内存中”编译的。您通常想要做的是将生成的类型缓存在字典中,以便可以重用。最大的性能影响是类型生成。创建该类型的实例可以非常便宜(取决于您如何做到这一点 - 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()();

使用此模式,您当您的应用程序刚刚启动时,您会看到性能下降,但随着缓存未命中的减少,它的性能几乎与内联代码一样好,

您可以使用类似的方法来生成 Invokers - 这里有一个很好的例子:

http://www.codeproject.com/KB/cs/FastMethodInvoker.aspx

Yes, it is possible to do this (create a proxy type with properties corresponding to method parameters) using Reflection.Emit. Once you have this, you could assign an instance of the proxy object to your PropertyGrid, and then use the values entered to call the method. What you want to do, however, is not trivial.

I would point you to the MSDN documentation for TypeBuilder for an example of creating a type using Reflection.Emit.

To answer your questions about performance, yes, the code is compiled "in memory". What you would typically want to do is cache the generated type in a dictionary so it can be reused. The biggest performance hit is on Type generation. Creating an instance of the type can be made to be very cheap (depending on how you do it - Activator.CreateInstance() being the slowest, something like this:

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;
    }

which allows you to create a new instance by simply calling

object obj = GetCreator()();

Using this pattern, you'll see degraded performance when your application is just getting started, but as the cache misses decrease it will perform almost as good as inline code.

You can use a similar method for generating Invokers - there is a pretty good example here:

http://www.codeproject.com/KB/cs/FastMethodInvoker.aspx

执手闯天涯 2025-01-02 18:56:34

这是或多或少相同的问题及其解决方案。它是为 .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;
}

Here is more or less same problem and it's solution. It's written for .NET 3.5 and works well. The goal was centralize all web methods in a single web service (.asmx) and call any registered methods from a single location. The code could be much more smaller. But, due to some forced conversion, it's a bit long.

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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文