通常,在编译表达式树时,我会想到不是基元类型或字符串的常量是不可能的 . 但是,这段代码:
public class A
{ public int mint = -1; }
public static void Main(String[] pArgs)
{
//Run(pArgs);
Action pact = Thing();
pact();
}
public static Action Thing()
{
var a = new A();
a.mint = -1;
LambdaExpression p =
Expression.Lambda<Action>(Expression.Assign(Expression.Field(Expression.Constant(a, typeof(A)), Strong.Instance<A>.Field<int>(b => b.mint)), Expression.Constant(3, typeof(int))));
return ((Expression<Action>)p).Compile();
}
不仅编译,而且实际运行!如果在Thing()方法中运行已编译的方法,那么实际上可以看到变量a将其字段从-1更改为3
我不明白这是否有意义/是可能的 . 方法如何引用其范围之外的局部变量(当检查Thing()的IL时,变量a只是一个标准的局部变量,而不是像堆上的那样在堆上) . 周围是否有某种隐藏的背景?当局部变量a可能已从堆栈中移除时,如何在Main中运行pact!
2 回答
它只是
a
是一个局部变量;实际对象(来自new A()
)始终在堆上 . 当你使用Expression.Constant(a, typeof(A))
时,你输入的不是a
常量 - 它是a
的值,即对象引用 . 所以:就树而言,a
的范围无关紧要 . 这实际上就是捕获变量(闭包)通常由编译器实现的方式(尽管你不允许赋值操作符),因此就表达式而言:这是常规操作 .作为使用C#表达式编译器的可比示例,see here,其中
编译为:
它不能,也不能 .
它有时可以引用局部变量指向的对象 .
是否可以取决于表达式编译或以其他方式使用的方式 .
表达式本身有三种方法将表达式编译成方法:
在
DynamicMethod
中使用Compile()
编译为IL .使用
CompileToMethod()
编译到IL(并非在所有版本中都可用 .使用
Compile()
编译成一组解释的指令,其中包含运行解释的thunk委托 .如果IL编译可用,则使用第一个,除非传递
true
以更喜欢解释(在具有该重载的那些版本上)并且解释也不可用 . 这里有一个数组用于闭包,它与在委托中关闭本地的数组非常相似 .第二个用于写入另一个程序集,不能以这种方式关闭 . 由于这个原因,许多适用于
Compile()
的常量将无法与CompileToMethod()
一起使用 .如果IL编译不可用,则使用第三个,或者在具有该重载的那些版本中传递
true
以优先解释 . 这里对对象的引用被放入"constants"数组中,然后解释器可以引用它 .另一种可能性是其他东西完全解释了表达,例如在生成SQL代码 . 通常,这将失败,除了字符串之外的非基本常量,但如果查询处理器知道常量的类型(例如,如果它是一种它知道的实体类型),那么代码生成等价的那个实体可以 生产环境 .