關於 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 的一個測試樣例運行過程中生成的表示式樹放上來……

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *

*

這個網站採用 Akismet 服務減少垃圾留言。進一步瞭解 Akismet 如何處理網站訪客的留言資料