2013年8月4日 星期日

好用的 ConditionalAttribute

在現實的專案中,如果你的產品行銷合作對像不只一、兩個,可能就會產生一些奇特的需求,例如包給某廠商的版本要比一般官網的版本多一些認證流程、在 Registry 中寫入不同的值做識別之類的,更常見的還有在 Debug 階段需要執行寫 Log 的流程這種需求。

在以前寫 MFC 時很習慣的是用 #ifdef _DEBUG 和 #endif 把程式碼包起來,在撰寫 C# 時也很習慣的去查了同樣功能的語法,也就是 #if DEBUG 和 #endif,但這種方法和 #ifdef 有一樣的缺點,如果我們把 Function 包起來,呼叫 Function 的地方沒包到,編譯當然就不會過,如果我們加完某流程後沒有一個一個 Configuration 去做測試,這種狀況往往在跑 CI 時才會被發現,一來一回頗浪費時間。

最近看書學到了一招 System.Diagnostics.ConditionalAttribute

先來看看用 #if #endif 在漏包了 call Function 時的狀況,這種狀況下在 Debug Build 時,前置處理器會將 #if DEBUG 所包起來的部份拿掉,所以最後一行沒有包到的部份就會發生編譯錯誤。

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        InitParams();
#if DEBUG
        InitParamsWithDebug();
#endif
        InitParamsWithDebug(); // Release 編譯會不過,因為沒有 InitParamsWithDebug 這個 function
    }

#if DEBUG
    public void InitParamsWithDebug()
    {
    }
#endif

    public void InitParams()
    {
    }
}

這時我們改用 ConditionalAttribute 來描述 function 的寫法如下,這種方式下,前置處理器會拿掉的不是該 Function,而是 call 此 Function 的部份程式碼,所以在沒有 DEBUG 這個 Symbol 的 Configration 時,被前置處理器移除的是 InitParamsWithDebug(n); 這行,相對的方便許多,也有利於我們將單一功能的程式抽離成為一個獨立的 Function,除此之外, n 這個變數的宣告也會一併被移除,這算是蠻可惜的一點

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        InitParams();
        int n = 0; // Release 編譯完後此行會不見
        InitParamsWithDebug(n); // Release 編譯完後此行會不見
        n = 9; // 比較可惜的是這行也不見了 XD
    }

    [Conditional("DEBUG")]
    public void InitParamsWithDebug(int i)
    {
        // Release 編譯完後此 function 依然會存在
    }

    public void InitParams()
    {
    }
}

當然,如果我們有多個 Configuration 會執行同一個 Function 或使用某一個 Class 的狀況下,加上多個 Conditional 相當於用 or 串接條件這種做法是可以的,相當的方便,例如

[Conditional("BUILD_FOR_MANUFACTURER_A"), Conditional("BUILD_FOR_MANUFACTURER_B")]
public void ShowManufacturerLogo()
{
}

沒有留言:

張貼留言