【问题标题】:What is captured in Expression.CompileExpression.Compile 中捕获的内容
【发布时间】:2016-02-05 11:24:46
【问题描述】:

假设我有一个带有属性栏的类型 Foo。

我得到了以下方法:

public static void DumpValue<T>(Expression<Func<T>> expr)
{
     MemberExpression memberExpression = expression.Body as MemberExpression;
     Debug.WriteLine("{0} => {1}", memberExpression.Member.Name, expr.Compile()());
}

它是这样使用的:

Foo a = new Foo{Bar ="Hello"};
Foo b = new Foo{Bar ="World"};
DumpValue(() => a.Test);
DumpValue(() => b.Test);

它给出了输出:

Bar => Hello
Bar => World

我的问题涉及连续的编译调用。返工 Func 是否足够聪明 进入像 Func (内部)这样的东西,所以实例被删除,所以它可以用于 Foo 的任何实例,并且只有生成的 Delegate 是实例特定的?或者它确实为每个实例完全编译它?

如果是后者,我是否需要担心大量编译函数会污染内存,我的测试看不到任何影响。

我知道这可以通过重写 DumpValue 来避免,但我想知道幕后发生了什么。这里只是举例说明。

我在Source 中挖了一条路,但找不到任何线索。

改写这个问题: 编译器是否优化了实例并在此处缓存了一些信息,并且仅将实例烘焙到最终委托中,还是“一直”进行?

【问题讨论】:

  • 每次调用.Compile()都会编译新的DynamicMethod
  • 并且每个“编译”Expression 都连接到Foo 的不同实例(ab

标签: c# lambda expression expression-trees


【解决方案1】:

我的问题与编译有关。返工是否足够聪明 将 Func 转换为 Func 之类的东西,因此删除了实例 所以它可以用于 Foo 的任何实例?还是确实编译 每个实例都有吗?

当你编译一个表达式时,你会得到一个委托。如果您想在调用编译后的表达式时使用任意实例,则不需要Expression&lt;Func&lt;T&gt;&gt; 而需要Expression&lt;Func&lt;T, S&gt;&gt;

否则,您需要从头开始构建表达式树,以免在表达式主体中使用捕获的引用。

关于缓存的事情...

我已经修改了您的代码,以检查即使您编译两次相同的表达式树,您也会得到不同的委托实例:

using System;
using System.Linq.Expressions;

public class Program
{
    public static Delegate DumpValue<T>(Expression<Func<T>> expr)
    {
        MemberExpression memberExpression = expr.Body as MemberExpression;

        return expr.Compile();
    }

    public static void Main()
    {
        string a = "foo";
        string b = "bar";

        var del1 = DumpValue(() => a);
        var del2 = DumpValue(() => b);

        // FALSE
        Console.WriteLine(Object.ReferenceEquals(del1, del2));
    }
}

简而言之,没有缓存,正如我的回答从一开始就说明的那样,我怀疑这样的功能是否容易实现,因为它可能是非常用例和边缘情况,泛化表达式可能是一个时间 -消耗任务。 可能在大多数情况下,许多委托实例都比实现缓存算法(甚至是在编译它们之前的表达式树转换...)更好。

【讨论】:

  • 这并不能真正回答我的问题。我知道我可以重写表达式。我尝试改写我的问题,以便更清楚地知道编译器是否在幕后优化了实例。
  • @CSharpie 它确实回答了你的问题,但我刚刚在我的回答中添加了背景,只是不回答 no ;) 没有优化。表达式树类似于抽象语法树,但处于更高级的编译阶段。这就像在运行时构建 C# 代码而不进行解析。如果表达式访问引用,它将使用该引用,您无法更改。
  • 我相信你来得太快了。看到这个referencesource.microsoft.com/#System.Core/Microsoft/Scripting/…
  • @CSharpie 这不是私有的/内部的吗? :\
  • @CSharpie 关于您更新的问题,我们应该期待 Eric Lippert 的回答,不是吗?
【解决方案2】:

我会说任何地方都没有缓存。

Expression&lt;TDelegate&gt;.Compile() 调用 LambdaCompiler.Compile()。从那里创建LambdaCompiler 的一个新实例,它初始化_method 字段(带有var method = new DynamicMethod(lambda.Name ?? "lambda_method", lambda.ReturnType, parameterTypes, true);),然后将用于首先创建方法和委托。注意_method 是只读的,所以没有人可以改变它,它直接被LambdaCompiler.CreateDelegate() 用来创建委托。任何地方都没有缓存。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-26
    • 2015-08-19
    • 1970-01-01
    相关资源
    最近更新 更多