從Trace和Debug來看條件編譯(Conditional Compilation)
條件編譯,顧名思義,就是根據在編譯時指定的條件決定最後需要編譯的代碼。條件編譯是我們可以針對某些特性的環境編寫相應的代碼,比如有寫的代碼隻需要在Debug模式下才需要執行,有些代碼僅僅是為了在SIT或者UAT環境下有效地進行Troubleshooting,而在Production環境下則不應該執行。通過條件編譯機製,我們可以針對某中特定的“條件編譯符(Conditional Compilation Symbol)”編寫相應的代碼。在進行最終編譯的時候,通過指定的條件編譯符,編譯器判斷這些特殊的代碼是否應該被編譯。
目錄:
一、Trace.WriteLine() V.S. Debug.WriteLine()
二、一個重要的特性ConditionalAttribute
三、一個條件編譯的例子
四、看看編譯後的代碼
五、ConditionalAttribute與#if/#endif
為了讓大家對條件編譯有一個相對直觀的認識,我們舉一個大家很熟悉的例子。我們都知道,在Trace和Debug是定義在System.Diagnostics命名空間下兩個重要的用於應用程序“診斷”的類,我們可以通過它們的靜態方法Write或者WriteLine方法寫入一些追蹤和調試消息。如果你對Trace和Debug具有一定的了解,你應該知道定義在它們之中的Write或者WriteLine方法具有相同的實現,最終都是。
為了演示Trace和Debug消息寫入機製,我寫了一個非常簡單的程序。首先我通過繼承TraceListener,寫了一個自定義的TraceListener:ConsoleTraceListener。ConsoleTraceListener實現了抽象方法Write和WriteLine,直接將消息通過控製台打印出來。這個ConsoleTraceListener定義如下:
1: public class ConsoleTraceListener : TraceListener
2: {
3: public override void Write(string message)
4: {
5: Console.Write(message);
6: }
7:
8: public override void WriteLine(string message)
9: {
10: Console.WriteLine(message);
11: }
12: }
然後,我們通過下麵的配置,將這個自定的ConsoleTraceListener應用到.NET的Tracing體係中:
1: <?xml version="1.0"?>
2: <configuration>
3: <system.diagnostics>
4: <trace>
5: <listeners>
6: <add name="ConsoleTraceListener" type="Artech.ConditionalCompilation.ConsoleTraceListener, Artech.ConditionalCompilation"/>
7: </listeners>
8: </trace>
9: </system.diagnostics>
10: </configuration>
最後我們編寫如下的代碼,分別調用Debug和Trace的WriteLine方法寫入一段指定的消息:
1: static void Main(string[] args)
2: {
3: Trace.WriteLine("This is message written by invoking Trace.WriteLine method.");
4: Debug.WriteLine("This is message written by invoking Debug.WriteLine method.");
5: }
我們指定的消息將會通過ConsoleTraceListener直接寫入到控製台上:
1: This is message written by invoking Trace.WriteLine method.
2: This is message written by invoking Debug.WriteLine method.
從上麵的例子,我們基本上可以看出定義在Trace和Debug中的WriteLine方法在實現上並沒有什麼不同之處,最終的診斷消息的寫入操作都是通過配置好的TraceListener列表來完成的。如果你通過Reflector來看看WriteLine方法在兩者中的實現,你更會發現方法的。
1: public static class Debug
2: {
3: //...
4: [Conditional("DEBUG")]
5: public static void WriteLine(string message)
6: {
7: TraceInternal.WriteLine(message);
8: }
9: }
10:
11: public sealed class Trace
12: {
13: //...
14: [Conditional("TRACE")]
15: public static void WriteLine(string message)
16: {
17: TraceInternal.WriteLine(message);
18: }
19: }
不但WriteLine方法在Trace和Debug中的實現一樣,而且這兩個方法均具有一個相同的特性,所不同的ConditionalAttrite中具有不同的參數,分別是和。這個特殊的ConditionalAttribute特性就涉及到我們今天討論的主題:條件編譯,這個特性中指定的參數(DEBUG和TRACE)就是我們之前說的。
當我們調用一個應用了ConditionalAttribute特性的方法,在編譯的時候,。比如說,我們調用Trace.WriteLine方法,但是在編譯的時候我們沒有指定TRACE這個條件編譯符,在最終編譯的程序集中,是沒有這句代碼的。
C#和VB.NET編譯器(csc.exe, vbc.exe)定義相應的命令行參數使你利用指定條件編譯符。如果你完全采用VS進行編譯,在默認的情況下,TRACE這個條件編譯符會自動會包含進行,在下條件編譯符DEBUG會被包含進來,而則不會。你可以通過項目屬性對話框的Build頁選擇是否需要包含DEBUG和TRACE這兩個條件編譯符,你也可以定義你自己的條件編譯符。比如下麵的設置中,我選擇包含DEBUG和TRACE這兩個條件編譯符,同時自定義了一個新的條件編譯符:UAT,表明本次編譯環境為用戶接收測試。
為了更好地說明條件編譯的意義,我寫了另一個小小的例子。場景時這樣的:有些邏輯需要在被授權的條件下才能被指定,但是為了測試方便(測試人員可以采用匿名用戶進行測試),我們希望授權的檢查隻有在Production環境下才生效,開發、SIT和UAT階段則不需要。我們就可以通過條件編譯機製來解決這個問題。
首先我們簡化授權的邏輯,假設隻有具有Admin角色的用戶才是授權的用戶。這樣的授權邏輯被定義在如下的Authorize方法中,在該方法上應用了ContitionalAttribute特性,並將作為參數的條件編譯符定義成PRODUCTION,表明這個方法隻有在Production環境中有效。
1: [Conditional("PRODUCTION")]
2: public static void Authorize()
3: {
4: if (!Thread.CurrentPrincipal.IsInRole("Admin"))
5: {
6: throw new SecurityException("Access denied!");
7: }
8: }
這個Authorize方法會在如下的情況下被調用:當前線程被賦予了一個角色列表為空的GenericPrincipal對象。
1: static void Main(string[] args)
2: {
3: var identity = new GenericIdentity("Foo");
4: var principal = new GenericPrincipal(identity, new string[0]);
5: Thread.CurrentPrincipal = principal;
6: Authorize();
7: Console.WriteLine("Continue...");
8: }
在默認的情況下(沒有顯式指定條件編譯符),我們定義的授權檢查不會發生,運行我們的程序,。但是,如果我們通過VS的項目屬性對話框,自定義一個PRODUCTION條件編譯符,再次運行程序,定義在Authorize方法中的授權檢驗將會生效。下麵是輸出結果:
1: Unhandled Exception: System.Security.SecurityException: Access denied!
2: at Artech.ConditionalCompilation.Program.Authorize() in E:\Others\Conditional
3: Compilation\Program.cs:line 28
4: at Artech.ConditionalCompilation.Program.Main(String[] args) in E:\Others\Con
5: ditionalCompilation\Program.cs:line 19
我們之前已經說了,條件編譯就是在編譯的時候將指定的條件編譯符動態去過濾不需要參與編譯的源代碼。對於調用了ConditionalAttribute特性的方法,隻有裏麵的參數和指定的條件編譯符一致,相應的代碼才會參與編譯。以上麵的代碼為例,在我們沒有指定PRODUCTION條件編譯符的情況下,編譯出來包含在程序集中的代碼等同於下麵:
1: private static void Main(string[] args)
2: {
3: GenericIdentity identity = new GenericIdentity("Foo");
4: GenericPrincipal principal = new GenericPrincipal(identity, new string[0]);
5: Thread.CurrentPrincipal = principal;
6: Console.WriteLine("Continue...");
7: }
我個人推薦盡量將條件編譯的代碼封裝到一個方法中,並在上麵應用ConditionalAttribute特性。如果不能,才使用#if/#endif這樣的條件編譯指令。如果我們采用內聯的方式來實現基於上麵的授權檢驗,我們可以直接使用#if/#endif塊來封裝授權邏輯。相應的代碼如下:
1: static void Main(string[] args)
2: {
3: var identity = new GenericIdentity("Foo");
4: var principal = new GenericPrincipal(identity, new string[0]);
5: Thread.CurrentPrincipal = principal;
6: #if PRODUCTION
7: if (!Thread.CurrentPrincipal.IsInRole("Admin"))
8: {
9: throw new SecurityException("Access denied!");
10: }
11: #endif
12: Console.WriteLine("Continue...");
13: }
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
最後更新:2017-10-27 11:04:21