如果想要执行由表达式树表示的 .NET 代码,则必须将其转换为可执行的 IL 指令。
由于此类型映射到一个委托类型,因此 .NET 可以检查表达式,并为匹配 lambda 表达式签名的适当委托生成 IL。
对于具有任何返回类型和参数列表的 Lambda 表达式,存在这样的委托类型:该类型是由该 Lambda 表达式表示的可执行代码的目标类型。
请注意,CompileToMethod 仅在完整的桌面框架中可用,不能用于 .NET Core。
这让你可以将表达式树转换为委托对象,并拥有生成的委托的完整调试信息。
使用下面的代码将表达式转换为委托:
Expression<Func<int>> add = () => 1 + 2; var func = add.Compile(); // 生产委托 var answer = func(); // 执行委托 Console.WriteLine(answer);
必须将其转换为正确的委托类型,以便使任何编译时工具检查参数列表或返回类型。
通过调用 func() 调用该委托将执行代码。
表达式树是不可变的,且在之后编译同一表达式树将创建执行相同代码的委托。)
通过避免对 LambdaExpression.Compile() 的任何额外调用所节省的计算时间将多于执行代码(该代码确定可导致相同可执行代码的两个不同表达式树)所花费的时间。
但是,即使是执行这个简单的操作,也存在一些必须注意的事项。
必须保证作为委托的一部分的任何变量在调用 Compile 的位置处和执行结果委托时可用。
但是,如果表达式访问实现 IDisposable 的变量,则代码可能在表达式树仍保留有对象时释放该对象。
例如,此代码工作正常,因为 int 不实现 IDisposable:
private static Func<int, int> CreateBoundFunc() { var constant = 5; // 常量由表达式树捕获 Expression<Func<int, int>> expression = (b) => constant + b; var rVal = expression.Compile(); return rVal; }
在稍后执行 CreateBoundFunc 返回的函数之后,可随时访问该变量。
但是,请考虑实现 IDisposable 的此(人为设计的)类:
public class Resource : IDisposable { private bool isDisposed = false; public int Argument { get { if (!isDisposed) return 5; else throw new ObjectDisposedException("Resource"); } } public void Dispose() { isDisposed = true; } }
如果将其用于如下所示的表达式中,则在执行 Resource.Argument 属性引用的代码时将出现 ObjectDisposedException:
private static Func<int, int> CreateBoundResource() { using (var constant = new Resource()) // 常量由表达式树捕获 { Expression<Func<int, int>> expression = (b) => constant.Argument + b; var rVal = expression.Compile(); return rVal; } }
(它已被释放,因为它已在 using 语句中进行声明。)
现在,在执行从此方法返回的委托时,将在执行时引发 ObjectDisposedException。
出现表示编译时构造的运行时错误确实很奇怪,但这是使用表达式树时的正常现象。
定义表达式时,请谨慎访问局部变量,且在创建可由公共 API 返回的表达式树时,谨慎访问当前对象(由 this 表示)中的状态。
在它不存在的情况下,将遇到 ReferencedAssemblyNotFoundException。
这提供了一种机制,用于执行表达式树所表示的代码。
如果未按预期进行,那么错误也是很容易预知的,并且将在使用表达式树的任何代码的第一个测试中捕获这些错误。