通常,当编译一个表达式树时,我会认为不是原始类型或字符串的常量是不可能的。但是,此代码:
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()方法中运行已编译的方法,则实际上可以看到该变量,并将其字段从-1更改为3
我不知道这有什么意义/有可能。方法如何引用超出其范围的局部变量(检查Thing()的IL时,变量a只是标准局部变量,而不是像闭包那样在堆上)。过去是否存在某种隐藏的上下文?当局部变量a可能已从堆栈中删除后,pact如何在Main中运行!
答案 0 :(得分:1)
方法如何引用超出其范围的局部变量
不能,也不能。
有时 可以引用局部变量指向的对象。
它是否取决于表达式的编译方式或使用方式。
表达式本身可以通过三种方式将表达式编译为方法:
Compile()
中用DynamicMethod
编译为IL。CompileToMethod()
编译为IL(并非在所有版本中都可用。Compile()
编译为一组解释的指令,并带有运行该解释的thunk委托。如果IL编译可用,则使用第一个,除非通过true
来首选解释(在具有该重载的版本上)并且解释也不可用。在这里,数组用于闭包,它与在委托中关闭本地数组非常相似。
第二个用于写入另一个程序集,无法以这种方式关闭。由于这个原因,许多将与Compile()
一起使用的常量将不适用于CompileToMethod()
。
如果IL编译不可用,或者在那些具有重载的版本中传递了true
而不喜欢解释,则使用第三个。在这里,对对象的引用被放入解释器可以引用的“常量”数组中。
另一种可能性是,有些东西会完全解释该表达式,例如在生产SQL代码。通常,尽管使用字符串以外的非基本常量,这将失败,但是,如果查询处理器知道该常量的类型(例如,如果它是它所知道的实体的类型),则进行编码以产生与该常量相等的值实体可以生产。
答案 1 :(得分:0)
只有a
是局部变量;实际的 object (来自new A()
)总是在堆上。当您使用Expression.Constant(a, typeof(A))
时,并不是将a
作为常量输入-它是a
的 value ,即对象引用。因此:就树而言,a
的范围无关紧要。实际上,这正是编译器通常实现捕获的变量(闭包)的方式(尽管您通常看不到,并且基于C#的表达式编译器不允许赋值运算符),因此就表达式树而言:这与往常一样。
作为使用C#表达式编译器see here的可比示例,其中
public void M() {
int mint = -1;
Expression<Func<int>> lambda = () => mint;
}
编译为:
[CompilerGenerated]
private sealed class <>c__DisplayClass0_0
{
public int mint;
}
public void M()
{
<>c__DisplayClass0_0 <>c__DisplayClass0_ = new <>c__DisplayClass0_0();
<>c__DisplayClass0_.mint = -1;
Expression.Lambda<Func<int>>(Expression.Field(Expression.Constant(<>c__DisplayClass0_, typeof(<>c__DisplayClass0_0)), FieldInfo.GetFieldFromHandle((RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/)), Array.Empty<ParameterExpression>());
}