Expression.Compile 中捕获的内容

What is captured in Expression.Compile

假设我有一个 class 类型的 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 的任何实例并且只有生成的委托是特定于实例的?或者它确实为每个实例完全编译它?

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

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

我在 Source 中摸索,但找不到任何线索。

改写这个问题: 编译器是优化实例并在此处缓存一些信息,只将实例烘焙到最终委托中,还是他去 "all the way all the time"?

My Question here concerns the Compile. Is it smart enough to rework the Func Into something like Func so the instance is removed so it can be used for any instance of Foo ? Or does it indeed Compile it for each instance?

当你编译一个表达式时,你会得到一个 委托。如果你想在调用编译表达式时使用任意实例,那么你不需要Expression<Func<T>>而是Expression<Func<T, S>>

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

关于缓存的事情...

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

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));
    }
}

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

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

Expression<TDelegate>.Compile() calls LambdaCompiler.Compile(). From there a new instance of LambdaCompiler is created, that initializes the _method field (with a var method = new DynamicMethod(lambda.Name ?? "lambda_method", lambda.ReturnType, parameterTypes, true);) that will be then used to create first the method and then the delegate. Note that _method is readonly, so no one can change it, and is directly used by LambdaCompiler.CreateDelegate() 创建委托。任何地方都没有缓存。