在 Expression.Compile 中捕获的内容

本文关键字:Expression Compile | 更新日期: 2023-09-27 18:33:18

假设我得到了一个带有属性栏的 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,T> 这样的东西(内部(,所以实例被删除,以便它可以用于 Foo 的任何实例,只有生成的委托是特定于实例的?还是确实为每个实例完全编译它?

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

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

我翻遍了源头,但找不到任何线索。

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

在 Expression.Compile 中捕获的内容

一直"一直"?

我在这里的问题与编译有关。它是否足够聪明,可以返工 将 Func 转换为类似于 Func 的内容,以便删除实例 所以它可以用于Foo的任何实例?还是确实编译 它适用于每个实例?

编译表达式时,会得到一个委托。如果要在调用编译后的表达式时使用任意实例,则不需要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()打电话给LambdaCompiler.Compile().从那里创建一个新的LambdaCompiler实例,该实例初始化_method字段(带有var method = new DynamicMethod(lambda.Name ?? "lambda_method", lambda.ReturnType, parameterTypes, true);(,然后用于首先创建方法,然后创建委托。请注意,_method是只读的,因此没有人可以更改它,LambdaCompiler.CreateDelegate()直接使用它来创建委托。任何地方都没有缓存。