Linq表达式树编译非平凡对象常量并以某种方式引用它们

时间:2018-12-10 10:09:17

标签: c# linq compiler-construction expression-trees

通常,当编译一个表达式树时,我会认为不是原始类型或字符串的常量是不可能的。但是,此代码:

   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中运行!

2 个答案:

答案 0 :(得分:1)

  

方法如何引用超出其范围的局部变量

不能,也不能。

有时 可以引用局部变量指向的对象。

它是否取决于表达式的编译方式或使用方式。

表达式本身可以通过三种方式将表达式编译为方法:

  1. Compile()中用DynamicMethod编译为IL。
  2. 使用CompileToMethod()编译为IL(并非在所有版本中都可用。
  3. 使用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>());
}