关于 Linq.Expression 的一些调试技巧

使用 System.Linq.Expression 可以非常方便地在应用程序运行时动态生成 IL 代码,从而构造动态方法。相比于 System.Reflection.Emit.ILGenerator,使用表达式树构造动态方法要比使用 IL 代码更为人性化。至于原因嘛……只能说,复杂的函数用 IL 指令自己编写……我做不到 TT

当然,也可以使用 CodeDOM 来编写动态类型(以及动态方法),然而,由于 CodeDOM 相当于是根据 DOM图 生成了代码,然后调用编译器对代码重新进行编译,因此会存在一定的效率问题(c.f. Reflection.Emit vs CodeDOM

还好,从 .NET Framework 4.0 开始,可以使用表达式树生成动态方法。例如,一个简单的输入日期/时间的例子

运行结果如下

这样要比手动编写 IL 汇编更为容易。可以说是在动态方法上迈进了一大步。然而,使用动态生成的委托也为调试带来了一定的麻烦。例如,同样是上面的代码,如果我们输入的日期格式不正确,那么会引发异常:

是的,根据这样的提示,我们可以判定是lambda_method的里面发生了异常,在这个案例中,由于代码较为简单,因此查错非常容易。然而,当你的表达式树非常复杂时,仅仅知道异常发生在lambda_method的内部是远远不够的。而根据现有的调试手段,是无法进入动态方法内部的。因此,只能使用一些间接的调试手段,例如插入测试函数调用:

或者,我们也可以更为直接,例如调用Debug.Print。对于以上代码,如果接收到错误输入,那么应该会有如下输出:

实际上,如果我们在TestPoint函数中插入断点,运行到断点后,可以看到如下的调用堆栈:

这从另一个方面反映了动态生成的函数真的没法调试。

Linq.Expreesion在设计时已经考虑到了调试的问题,所以引入了一个辅助属性。当你在DEBUG模式下时,可以在Expression实例中找到这个DebugInfo属性。它包含了这个Expression所包含的表达式的文本表示。(c.f. 调试表达式树

使用文本可视化工具,可以看到DebugView属性的完整内容:

关于DebugView属性中各个表达式的具体含义,可以参阅MSDN上的专题:调试表达式树

最后,把我之前写的 XSerializer 的一个测试样例运行过程中生成的表达式树放上来……

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.