我正在开发一个允许用户输入任意表达式的库。然后,我的库将这些表达式作为较大表达式的一部分编译到委托中。现在,由于未知的原因,用Compile
编译表达式有时/经常会导致代码比没有编译表达式时慢得多。我之前问过一个关于这个问题的问题,一个解决方法是不使用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;
}
}
}
问题不在于权限,因为没有权限可以允许您访问非公共字段或另一个类的成员而不进行反射。这类似于编译两个非动态程序集,其中一个程序集调用第二个程序集中的公共方法的情况。然后,如果在不重新编译第一个程序集的情况下将方法更改为private,那么第一个程序集调用现在将在运行时失败。换句话说,动态程序集中的表达式被编译成一个普通的方法调用,即使在同一个程序集中,它也没有从另一个类调用的权限。
由于没有权限可以解决您的问题,您可以将非公共字段和方法引用转换为使用反射的子表达式。
这是取自您的测试用例的一个示例。这个操作失败:
Expression<Func<int>> expression = () => 10 + GetValue();
但这将成功:
Expression<Func<int>> expression = () => 10 + (int)typeof(Program).GetMethod("GetValue", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, null);
由于这不会因异常而崩溃,您可以看到动态程序集确实具有反射权限,并且它可以访问私有方法,它只是不能使用CompileToMethod
导致的普通方法调用。
我曾经在使用DynamicMethod从生成的IL代码中访问类的私有元素时遇到了一个问题。
结果是类DynamicMethod
的构造函数有一个重载,该构造函数接收允许私有访问的类的类型:
此链接包含如何访问私有数据的示例…我知道这与表达式树没有任何关系,但它可能会给你一些关于如何做到这一点的线索。
可能在编译表达式树时有一些类似的事情…
如果非动态程序集是由您构建的,您实际上可以为动态程序集包含InternalsVisibleTo
(甚至可以使用强名称)。这将允许使用内部成员,这在您的情况下可能足够了?
如果这种方法还不够,我将结合Rick和Miguel的建议:为每个非公共成员的调用创建"代理"DynamicMethods,并更改表达式树,以便使用它们而不是原始调用。
您可以使用未记录的属性IgnoreAccessCheckTo
,它类似于InternalsVisibleTo
,但在想要访问它的程序集中。