使用 StackFrame 來取得目前執行中方法的資訊

這個資訊是 .NET Framework CLR 中的呼叫堆疊(Call Stack)中的資訊,可用來追蹤程式呼叫的順序以及由誰呼叫了什麼函式,通常都會使用 Exception.StackTrace 來抓出程式的哪裡擲出例外,但其實它還有個妙用,就是取得目前程式執行所在的方法資料,以及其歷程記錄。而且重要的是,它可以同時適用於 Debug 與 Release 模式。

在程式執行中如果擲出例外時,通常都會看到這樣的資訊:

這個資訊是 .NET Framework CLR 中的呼叫堆疊(Call Stack)中的資訊,可用來追蹤程式呼叫的順序以及由誰呼叫了什麼函式,通常都會使用 Exception.StackTrace 來抓出程式的哪裡擲出例外,但其實它還有個妙用,就是取得目前程式執行所在的方法資料,以及其歷程記錄。而且重要的是,它可以同時適用於 Debug 與 Release 模式。

在大多數的情況中,其實我們是不需要去擷取目前程式執行所在的方法名稱或參數的,不過某些情況下例外,像是:

  • 我的屬性或方法需要依賴前一個方法的資訊(例如方法名稱,參數或自訂屬性等)。
  • 在我的屬性或方法中取用類別中套用的自訂屬性。
  • 限制呼叫端的名稱或是組件資訊。

例如,為了要在設定屬性值的時候檢查值的範圍(例如限制整數只能在 50-100 之間),我們使用 Attribute 的方式寫了一個驗證用類別(若不知道什麼叫Attribute,可以參考善用 System.Attribute,讓你的元件更具彈性):

然後在程式中套用:

接著寫一支用戶端程式:

原本預期是在 testclass.Value = 20 時會擲出例外(因為不符值範圍),不過跑到 testclass.Value = 75 這一行時卻擲出了 "索引在預期的範圍之外" 的錯誤,經檢查發現是這一行擲出的例外:

使用除錯器去追蹤時,會發現 this.GetType().GetCustomAttributes() 會傳回一個 object 的空陣列,表示沒有捉到自訂的屬性資料,因為 this.GetType() 會傳回類別自己的資訊,但卻不會傳回屬性自己的資訊,因此會抓不到屬性所套用的自訂屬性資訊,不過遍尋 .NET Framework 中的 Reflection 方法,都沒有任何一個方式有提供可以抓取執行中方法資訊的能力,後來在網路上搜尋了一下,發現 StackTrace 和 StackFrame 可以做到這個工作。

StackTrace 包含了執行期的堆疊資訊,利用 FrameCount 得到堆疊中的數量,用 GetFrame() 來取得特定位置的方法資訊,這兩個方式都可以適用於 Debug 和 Release 模式,但它有部份資訊(像是 StackFrame.GetFileName(), StackFrame.GetFileLineNumber() 這類的)只能在 Debug 模式中使用,因此盡量不要在程式碼中去抓取這些資訊,否則當程式轉成 Release 模式時,這些資訊全部都會失效。

若只要取得當下執行方法的內容時,只要利用 new StackFrame() 就可以了,它自動會傳回目前執行方法的資訊,而不用去透過 StackTrace。

然後修改一下程式碼:

這裡的修改要特別注意的一點是,在 .NET Framework 中,屬性會自動被轉換成方法(在編譯時期轉換),其名稱是 "get_XXXX" 或 "set_XXXX"(分別代表 get 和 set),因此 StackFrame.GetMethod().Name 會傳回 "get_XXXX" 或 "set_XXXX" 名稱,要搭配 GetProperty() 取得屬性資訊時,要先把 "get_" 和 "set_" 濾掉,否則會抓不到屬性資訊。

這樣修改完之後再執行,就可以如我們預期的行為了。你也可以用 Release 模式來測試,亦可得到相同的結果。