【问题标题】:.NET: Accessing non-public members from a dynamic assembly.NET:从动态程序集中访问非公共成员
【发布时间】:2011-04-22 19:00:32
【问题描述】:

我正在开发一个允许用户输入任意表达式的库。然后,我的库将这些表达式作为更大表达式的一部分编译到委托中。现在,由于仍然未知的原因,使用Compile 编译表达式有时/经常导致代码比它不是编译表达式时要慢得多。我之前asked a question about this,一种解决方法是不使用Compile,而是使用CompileToMethod,并在新动态程序集中的新类型上创建static 方法。这行得通,而且代码很快。

但是用户可以输入任意表达式,结果如果用户调用非公共函数或访问表达式中的非公共字段,则会抛出System.MethodAccessException(在非公共方法的情况下) ) 当委托被调用时。

我在这里可能做的是创建一个新的ExpressionVisitor,它检查表达式是否访问任何非公开的内容,并在这些情况下使用较慢的Compile,但我宁愿让动态程序集以某种方式获得访问非公共成员的权利。或者找出我可以对Compile 变慢(有时)做些什么。

重现此问题的完整代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;

namespace DynamicAssembly
{
  public class Program
  {
    private static int GetValue()
    {
      return 1;
    }

    public static int GetValuePublic()
    {
      return 1;
    }

    public static int Foo;

    static void Main(string[] args)
    {
      Expression<Func<int>> expression = () => 10 + GetValue();

      Foo = expression.Compile()();

      Console.WriteLine("This works, value: " + Foo);

      Expression<Func<int>> expressionPublic = () => 10 + GetValuePublic();

      var compiledDynamicAssemblyPublic = (Func<int>)CompileExpression(expressionPublic);

      Foo = compiledDynamicAssemblyPublic();

      Console.WriteLine("This works too, value: " + Foo);

      var compiledDynamicAssemblyNonPublic = (Func<int>)CompileExpression(expression);

      Console.WriteLine("This crashes");

      Foo = compiledDynamicAssemblyNonPublic();
    }

    static Delegate CompileExpression(LambdaExpression expression)
    {
      var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(
        new AssemblyName("MyAssembly"+ Guid.NewGuid().ToString("N")), 
        AssemblyBuilderAccess.Run);

      var moduleBuilder = assemblyBuilder.DefineDynamicModule("Module");

      var typeBuilder = moduleBuilder.DefineType("MyType", TypeAttributes.Public);

      var methodBuilder = typeBuilder.DefineMethod("MyMethod", 
        MethodAttributes.Public | MethodAttributes.Static);

      expression.CompileToMethod(methodBuilder);

      var resultingType = typeBuilder.CreateType();

      var function = Delegate.CreateDelegate(expression.Type, 
        resultingType.GetMethod("MyMethod"));

      return function;
    }
  }
}

【问题讨论】:

  • 我没有给你答案,但是为什么要支持调用私有方法呢?
  • 因为用户期望它应该是可能的。因为当他创建表达式时,它们可访问的,例如() =&gt; CallPrivateMethod(),但它们会在运行时失败。对他来说,没有任何迹象表明它在他运行它并且它崩溃和燃烧之前不起作用。这真的很糟糕,并且违反了“最少意外”的规则,所以我不能证明这样做是合理的,我将不得不接受缓慢的代码。
  • 有意义,如果用户是 C# 程序员(而不是有人在表单中输入表达式,例如)。您是否对已编译委托的发布模式和调试模式进行了基准测试?它们如何相互比较?
  • @jlew - 是的,它将成为程序员使用的库。至于编译表达式树的性能细节,我可以参考我的链接问题,其中有深入的:)
  • 这可能是一个半生不熟的想法,也许您可​​以通过将表达式树中对私有方法的调用替换为对作为参数传递给 MethodBuilder 方法的委托的调用来分割性能差异.然后,将私有方法包装在委托中,并将它们作为参数传递给您的新方法。这将解决 MethodAccess 问题,但谁知道它是否会否定性能优势。

标签: c# .net performance expression-trees dynamic-assemblies


【解决方案1】:

问题不在于权限,因为没有权限可以让您在不进行反射的情况下访问非公共字段或另一个类的成员。这类似于您编译两个非动态程序集并且一个程序集在第二个程序集中调用公共方法的情况。然后,如果您将方法更改为私有而不重新编译第一个程序集,那么第一个程序集调用现在将在运行时失败。换句话说,动态程序集中的表达式被编译成一个普通的方法调用,即使在同一个程序集中,它也没有权限从另一个类调用。

由于没有权限可以解决您的问题,您也许可以将非公共字段和方法引用转换为使用反射的子表达式。

这是一个取自您的测试用例的示例。这失败了:

Expression<Func<int>> expression = () => 10 + GetValue();

但这会成功:

Expression<Func<int>> expression = () => 10 + (int)typeof(Program).GetMethod("GetValue", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null);

由于这不会因异常而崩溃,因此您可以看到您的动态程序集确实具有反射权限并且它可以访问私有方法,它只是无法使用CompileToMethod 导致的普通方法调用来做到这一点。

【讨论】:

    【解决方案2】:

    我曾经在使用 DynamicMethod 从生成的 IL 代码中访问类的私有元素时遇到问题。

    事实证明,DynamicMethod 类的构造函数有一个重载,它接收到允许私有访问的类类型:

    http://msdn.microsoft.com/en-us/library/exczf7b9.aspx

    此链接包含如何访问私有数据的示例...我知道这与表达式树无关,但它可能会为您提供一些有关如何操作的线索。

    在编译表达式树时可能存在某种类似的事情......或者您可以将该表达式树创建为 DynamicMethod。

    【讨论】:

    • 谢谢。但是CompileToMethod只有一种调用方式:需要传递一个MethodBuilder的实例。而且您只能从TypeBuilder 获得MethodBuilder,并且为此需要ModuleBuilder,而对于那个,您需要AssemblyBuilder
    【解决方案3】:

    如果非动态程序集是由您构建的,您实际上可以为动态程序集包含InternalsVisibleTo(甚至可以使用强名称)。这将允许使用内部成员,在您的情况下这可能就足够了吗?

    为了得到一个想法,这里有一个例子,它显示了使 Moq 的动态装配能够使用来自另一个装配的内部东西的热: http://blog.ashmind.com/2008/05/09/mocking-internal-interfaces-with-moq/

    如果这种方法还不够,我会结合 Rick 和 Miguel 的建议:为对非公共成员的每次调用创建“代理”DynamicMethods 并更改表达式树,以便使用它们而不是原始调用。

    【讨论】:

      猜你喜欢
      • 2019-05-01
      • 2021-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-14
      • 2016-09-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多