【问题标题】:How do Expression Trees provide access to local variables? [duplicate]表达式树如何提供对局部变量的访问? [复制]
【发布时间】:2017-05-21 07:11:50
【问题描述】:

这个问题与闭包的工作原理无关。这个问题是关于 LINQ 如何决定在运行时可解析表达式中引用什么,以及评估什么并将其放入该表达式。

这个问题试图了解 LINQ 的工作原理,以另一种语言实现类似的东西。

考虑以下要转换为表达式树的 LINQ 查询:

var my_variable = "abc";
var qry = from x in source.Foo
          where x.SomeProp == my_variable
          select x.Bar;

由编译器映射成代码:

var qry = source.Foo
           .Where(x => x.SomeProp == my_variable)
           .Select(x => x.Bar);

当它被转换为表达式树时,LINQ 如何知道将哪个引用到表达式中,以及评估哪个并将结果放入表达式中?

例如,它如何知道评估 my_variable 并将结果放入 expressino,但将 x.SomeProp== 转换为 LINQ 表达式树的一部分?

C# 编译器是否有一个硬编码的特殊列表,其中列出了为 LINQ 引用的表达式? (即可以翻译成SQL的最外层操作,包括:==、!=、&&、||、、=、子字符串等)

【问题讨论】:

  • 这不是关于闭包如何捕获变量的问题,而是关于 LINQ 如何知道要捕获哪些表达式以及将哪些表达式编码为表达式的问题。
  • LINQ 永远不会关闭任何东西。您只是将 lambdas 传递给 LINQ 调用,而正是那些 与 LINQ 毫无关系的 lambdas 创建了闭包。如果你想知道 lambdas 是如何创建闭包的,你有副本。如果您不这样做,那么您的问题就没有任何问题。说你的问题不是重复的当你问的问题与重复的完全相同是没有意义的。
  • 也许我对“捕获”一词的使用令人困惑...... LINQ 实现能够看到未计算的表达式(如 +)以及局部变量和函数调用的结果。这就是我要问的。
  • 没有个表达式在创建表达式时会被计算,并且所有可以在任何时候被计算表达式的任何消费者。这also 与LINQ 无关。 lambda 将拥有关于代码是什么的所有信息,并且实际的查询提供者可以(如果它选择)将部分或全部表达式评估为他们认为合适的值。 LINQ 只接受表达式并将它们提供给查询提供程序。
  • 我明白了我的困惑以及为什么这与 LINQ 无关,而仅与表达式树有关。我在想表达式树只是一个 AST(lambda 文本的结构化表示),但它们还包含对实时程序对象的引用。对于出现在 lambda 中的捕获变量,表达式树包含一个 MemberExpression/ConstantExpression 对,它可以访问变量的框。

标签: c# linq


【解决方案1】:

仅通过检查就可以清楚地看出 .SomeProp 依赖于 x 并且 x 在表达式树解析时不是确定性的,因为它是先前函数 (where) 的输出。

my_variable 不是表达式树中任何表达式的输出,因此它的值可能在表达式树解析时已知,但即使已知它也很可能不会被“烘焙”,因为这会阻止编译的表达式树不会被重用,因此它只会被视为表达式树评估的输入值。

我没有反编译linq,但是你可以考虑下面的表达式树;

ExpressionTree myEx = new ExpressionTree(
   new MultiplicationExpression(
      new InputVariableExpression("@MyInputVar"),
      new ConstantExpression(22)
   )
);

为了评估你可以打电话

Dictionary<string, object> inputVars = new Dictionary<string, object>();
inputVars.Add("@MyInputVar",16);
int result = myEx.Evaluate(inputVars);

解析器可能会选择烘焙常量表达式,因为它“知道”它不能改变,但请考虑以下事项;

ExpressionTree anotherEx = new ExpressionTree(
   new AdditionExpression(
      myEx,
      new InputVariableExpression("@MyNextInputVar")
   )
);

这在概念上类似于在 Linq x =&gt; 中使用替换变量,其中 myEx 是存储的表达式树,但实际上不是表达式的结果。表达式解析器在执行之前无法独立知道 myEx 的值是什么。

Dictionary<string, object> inputVars = new Dictionary<string, object>();
inputVars.Add("@MyInputVar",16);
inputVars.Add("@MyNextInputVar",45);
int result = anotherEx.Evaluate(inputVars);

因此,此执行代码将在评估 anotherEx 期间固有地评估 myEx。如果myEx 中只有ConstantExpression 元素,它可能只被评估一次并且结果被缓存,但是因为它包含一个超出范围的InputVariableExpression 很明显,一次评估的结果不能被缓存以供后续使用.

【讨论】:

  • 这是最接近我的问题的答案。您是否建议 LINQ 对它评估和引用结果的内容以及它在不评估的情况下引用的内容有某种硬编码的评估规则?
  • 我从来没有反编译过Linq,但是我写了自己的表达式树解析和编译代码;是的 - 我检查了输入,如果它们是恒定的,我将它们烘焙进去,但如果它们可能是可变的,我没有。这使我可以稍后重用已编译的表达式并跳过编译/解析步骤。我将编辑我的回复以进行更详细的解释。
  • 我想知道反对票是为了什么?我猜不能取悦所有人:0)
  • 事实证明,“神奇”在于表达式树不仅仅是被动的 AST,它们还与正在运行的程序状态相关联......所以你可以查看文本 AST 信息,如变量名称,或者您可以评估与实时运行程序相关的表达式。
【解决方案2】:

这很容易测试:

int i = 1;
Func<int> func = () => i;
i = 2;
Console.WriteLine(func.Invoke());

这会打印出2,它告诉我们它存储了符号并且在对函数求值之前不求值。

【讨论】:

  • 请注意,这里是测试小代码片段的好地方:csharppad.com
  • 这没有回答他how does LINQ capture the value of my_variable的问题
  • 有趣! Invoke 如何获得 I 的值?通过使用反射爬上堆栈?如果您返回 func 并稍后进行评估,那将不起作用。是否强制将本地“int i=1”装箱?
  • 即使i 超出范围,它似乎也保留了指向i 的指针。它不是 Invoke 所做的,而是更多 func 是什么。基本上 C# 中的所有内容都是一个指针(需要引用),因此该函数不需要知道 i 的值,只需在哪里查找即可。据我所知,LINQ 使用与其他 c# 相同的功能系统。 LINQ 函数的输入谓词不需要是匿名的,它们通常只是因为这样写更快。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-08
  • 2018-06-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多