【问题标题】:Performance penalty of Reflection.EmitReflection.Emit 的性能损失
【发布时间】:2012-02-08 07:27:20
【问题描述】:

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

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

一个例子是这样的:

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

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

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

我已阅读 MSDN 杂志上关于 ProperyGrid 的两篇优秀文章:

ICustomTypeDescriptor, Part 1

ICustomTypeDescriptor, Part 2

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

可以支持这种情况吗?有没有简单易行的解决方案?

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

【问题讨论】:

  • 是什么阻止了您将 Expando 对象用于此类用途?
  • 编辑了我的问题 - 使用 3.5,我们没有 Expando。
  • 那么使用字典并使用反射 API 调用您的方法呢?我在我们的 CMS 中使用相同的东西来集中对特定类方法的任意 Web 方法调用。
  • 调用方法不是问题。让用户通过 PropertyGrid(或任何其他易于使用的 UI 控件)输入方法的参数是个问题。
  • 你不能遍历 PropertyGrid 项目并收集用户提供的数据吗?

标签: c# .net winforms propertygrid reflection.emit


【解决方案1】:

是的,可以使用 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()();

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

您可以使用类似的方法来生成调用程序 - 这里有一个很好的示例:

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

    • Osman,如果您使用反射来调用您的方法,那么您将受到巨大的性能影响。你应该考虑(如果你还没有,比如 FastMethodInvoker (www.codeproject.com/KB/cs/FastMethodInvoker.aspx)
    • @Joe:谢谢!实际上,我们一点也不觉得慢(web 方法没有那么多)。更重要的是,我们已经在我们的产品中迁移到新的代码库。所以,它或多或少已经过时了。但是,知道这样的事情显然是一件好事。再次感谢。
    猜你喜欢
    • 2019-02-09
    • 2018-04-17
    • 2012-08-23
    • 2016-12-20
    • 1970-01-01
    • 2010-12-17
    • 2018-08-22
    • 1970-01-01
    • 2023-04-03
    相关资源
    最近更新 更多