Linq 表达式树编译非平凡的对象常量并以某种方式引用它们
Linq Expression tree compiling non-trivial object constants and somehow referring to them
通常,在编译表达式树时,我会认为不是原始类型或字符串的常量是不可能的。但是,这段代码:
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();
}
不仅编译而且实际上 运行s!如果你 运行 Thing() 方法中的编译方法,那么你实际上可以看到变量 a 将其字段从 -1 更改为 3
我不明白这如何使 sense/is 成为可能。方法如何引用其范围之外的局部变量(在检查 Thing() 的 IL 时,变量 a 只是一个标准的局部变量,而不是像闭包那样在堆上)。周围是否有某种隐藏的背景?当局部变量 a 可能已从堆栈中删除时,如何在 Main 中约定 运行!
只有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>());
}
How can a method refer to a local variable outside its scope
不能,也不能。
它可以有时引用局部变量指向的对象。
是否可以取决于表达式的编译方式或其他使用方式。
表达式本身可以通过三种方式将表达式编译成方法:
- 用
Compile()
编译到 DynamicMethod
中的 IL。
- 使用
CompileToMethod()
编译为 IL(并非所有版本都可用。
- 使用运行解释的 thunk 委托将
Compile()
编译成一组解释的指令。
如果 IL 编译可用,则使用第一个,除非将 true
传递给首选解释(在具有该重载的那些版本上)并且解释也不可用。这里一个数组用于闭包,它与在委托中关闭本地 is 非常相似。
第二个用于写入另一个程序集,不能以这种方式关闭。由于这个原因,许多适用于 Compile()
的常量不适用于 CompileToMethod()
。
如果 IL 编译不可用,或者 true
在那些具有该重载以更喜欢解释的版本中传递,则使用第三个。这里对对象的引用被放入 "constants" 的数组中,解释器随后可以引用该数组。
另一种可能性是其他东西完全解释了这个表达式,例如在生成 SQL 代码时。通常这会失败,但非原始常量不是字符串,但如果查询处理器知道常量的类型(例如,如果它是它知道的实体类型),那么代码会产生等同于该常量的代码可以生产实体。
通常,在编译表达式树时,我会认为不是原始类型或字符串的常量是不可能的。但是,这段代码:
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();
}
不仅编译而且实际上 运行s!如果你 运行 Thing() 方法中的编译方法,那么你实际上可以看到变量 a 将其字段从 -1 更改为 3
我不明白这如何使 sense/is 成为可能。方法如何引用其范围之外的局部变量(在检查 Thing() 的 IL 时,变量 a 只是一个标准的局部变量,而不是像闭包那样在堆上)。周围是否有某种隐藏的背景?当局部变量 a 可能已从堆栈中删除时,如何在 Main 中约定 运行!
只有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>());
}
How can a method refer to a local variable outside its scope
不能,也不能。
它可以有时引用局部变量指向的对象。
是否可以取决于表达式的编译方式或其他使用方式。
表达式本身可以通过三种方式将表达式编译成方法:
- 用
Compile()
编译到DynamicMethod
中的 IL。 - 使用
CompileToMethod()
编译为 IL(并非所有版本都可用。 - 使用运行解释的 thunk 委托将
Compile()
编译成一组解释的指令。
如果 IL 编译可用,则使用第一个,除非将 true
传递给首选解释(在具有该重载的那些版本上)并且解释也不可用。这里一个数组用于闭包,它与在委托中关闭本地 is 非常相似。
第二个用于写入另一个程序集,不能以这种方式关闭。由于这个原因,许多适用于 Compile()
的常量不适用于 CompileToMethod()
。
如果 IL 编译不可用,或者 true
在那些具有该重载以更喜欢解释的版本中传递,则使用第三个。这里对对象的引用被放入 "constants" 的数组中,解释器随后可以引用该数组。
另一种可能性是其他东西完全解释了这个表达式,例如在生成 SQL 代码时。通常这会失败,但非原始常量不是字符串,但如果查询处理器知道常量的类型(例如,如果它是它知道的实体类型),那么代码会产生等同于该常量的代码可以生产实体。