今天介紹一則讓你的 C# 程式好維護且不容易改壞的小技巧。

寫程式時難免會有需要字串常數的場合,若不要想太多,"常數A"、"常數B"是最直覺的寫法。

這樣寫沒什麼問題,程式通常也會跑得好好的。但遇到常數要修改,就是對細心度、視力與耐性的考驗了。

配改善這個問題,宣告一堆 const string ConstA = "常數A"、const string ConstB = "..." 是種解法,但 C# 還有更犀利的武器。

來段程式做示範。假設我們的專案資料物件有個屬性 Stage 代表專案所處階段,其值有提案、執行、驗收、結案等幾種狀態。 不花腦筋的寫法是 project.Stage == "驗收"。另外,CloseAndArchive() 方法則有段檢核邏輯, 遇到 project.AcceptData 屬性為 null 時,要拋出 ArgumentException 錯誤並提示錯誤屬性名稱為 "AcceptData"。

沿用傳統的開發習慣,程式可能會像這樣:

public class ProjectData
{
    public string Id { get; set; }
    /* 其他專案資訊欄位(省略) */
    /// <summary>
    /// 專案階段:提案、執行、驗收、結案... 等
    /// </summary>
    public string Stage { get; set; }
    /// <summary>
    /// 驗收日期
    /// </summary>
    public DateTime? AcceptDate { get; set; }

}

public class ProjectService
{
    //結案歸檔
    public static void CloseAndArchive(ProjectData project)
    {
        if (project.Stage != "驗收")
        {
            throw new ApplicationException("專案未達[驗收]階段");
        }
        else if (project.AcceptDate == null)
        {
            throw new ArgumentException("Can't be null", "AcceptDate");
        }
        project.Stage = "結案";
        //將專案狀態更新至資料庫(省略)
    }
}

寫完程式,忽然接到規格變更通知,「驗收」要改為「驗收完成」,AcceptDate 屬性要更名為 UserAcceptDate,OK,算算共有四處要改。

改四個地方感覺還好,但這只是簡單範例,換成較複雜的中大型專案,包含這些字串常數的程式碼可能散落在數十個類別,甚至位於不同專案, 動用「搜尋/取代」整個 .sln 配合肉眼確認一一置換是唯一解,且一旦漏改就等著在執行階段爆炸,驚喜無窮。

面對這個問題,C# 有更好的解法。

以階段名稱為例,我們可為專案階段宣告一個列舉,Stages 當成 Stage 型別,要做設定或比對時可寫成prjoect.Stage = Stages.驗收 需要轉為字串使用時,則可用Stages.驗收.ToString(),這樣再也不怕打錯字埋雷,錯字在開發跟編譯階段就會現形。

另外,需要屬性名稱或變數名稱作字串使用的場合,別直接寫死 "變數名稱" 。C# 6.0 有個新利器 nameof, 寫成 nameof(變數名稱) 或 nameof(屬性名稱) 可將名稱字串轉成強型別寫法,除了不怕打錯字,更改名稱時亦會自動換掉。

於是,前述程式可改寫如下:

public enum Stages
{
    提案,
    審核,
    執行,
    驗收,
    結案
}

public class ProjectData
{
    public string Id { get; set; }
    /* 其他專案資訊欄位(省略) */
    /// <summary>
    /// 專案階段:提案、執行、驗收、結案... 等
    /// </summary>
    public Stages Stage { get; set; }
    /// <summary>
    /// 驗收日期
    /// </summary>
    public DateTime? AcceptDate { get; set; }

}

public class ProjectService
{
    //結案歸檔
    public static void CloseAndArchive(ProjectData project)
    {
        if (project.Stage != Stages.驗收)
        {
            throw new ApplicationException($"專案未達[{nameof(Stages.驗收)}]階段");
        }
        else if (project.AcceptDate == null)
        {
            throw new ArgumentException("Can't be null", nameof(project.AcceptDate));
        }
        project.Stage = Stages.結案;
        //將專案狀態更新至資料庫(省略)
    }
}

補充:列舉轉常數字串的 Stages.驗收.ToString() 可改用nameof(Stages.驗收),除了寫法稍簡潔外,nameof() 可以用於 const、方法參數預設值等需要「靜態期常數」(Compiled-Time Constant)的場合,適用範圍更廣。示範:

改用列舉及 nameof 後,一樣要改 Stage 名稱及 UserAcceptDate,我們只需要用 Visual Studio 的 Rename 功能將 Stages 列舉的「驗收」更名為「驗收完成」, 將 Project 類別的 AcceptDate 改成 UserAcceptDate,Visual Studio 會自動改完所有要修改的地方,萬無一失。

常打錯字或改程式漏東忘西的人,看到這裡應該熱淚盈眶了吧?善用 C# 小技巧,生活更美好~

Tips of using C# enum and nameof() insead of string constants to make your code robust and easy to maintain.


Comments

# by NoobBenYang

改列舉真的夭壽舒服乾淨,而且程式寫久了真的很討厭看到字串專用的橘色出現...橘色=有機會人為疏失...

# by Newguy

一樓,我們連代表空字串的””和斷行的”\r\n”都禁用⋯⋯ 只能用 string.Empty 和 Environment.NewLine

# by NoobBenYang

to Newguy 喔喔喔喔!!!Environment.NewLine! 學習了!! 意外的收穫~謝謝~

# by ByTIM

我想問下列舉,有辦法存英文嗎? 能把以下轉成列舉嗎? 還是要搭個數字轉英文的函式? public const string _LEVEL_A = "A", //國小 _LEVEL_B = "B", //國中 _LEVEL_C = "C", //高中 _LEVEL_E = "E"; //大學

# by Jeffrey

to ByTIM,不太確定你的問題,以下寫法OK嗎? public enum Levels { A, B, C, D }

# by ByTIM

TO Jeffrey: 我比較想像樓主這樣,public enum Levels { 國小="A", 國中="B", 高中="C",大學="D"},不過這樣會發生錯誤, 只能用你的寫法,之後 nameof(Levels.A) 轉換,不能直接用 Levels.A。 目前: public enum Levels { [Description("國小")] A, [Description("國中")] B, [Description("高中")] C, [Description("大學")] D } //讀數值 string Level=nameof(Levels.A); 現在在研究這篇 [.NET] 如何取得 Enum 的 Description 描述字串,看要怎麼加擴充方法,來讀Description 的描述,這樣就能向下面那樣。 未來: public enum Levels { [Description("A")] 國小, [Description("B")] 國中, [Description("C")] 高中, [Description("E")] 大學 } //讀數值 string Level=Levels.國小.GetDescriptionText(); 總結:目前的寫法,還是要記住ABCE是甚麼,之後演變成未來的寫法,會一目了然。

# by Jeffrey

bo ByTIM, 若列舉對應的都是單一英文字元,我有個取巧解法: public enum Levels { 國小 = 0x41, //A 國中 = 0x42, //B 高中 = 0x43, //C 大學 = 0x44 //D } static void Main(string[] args) { Console.WriteLine(nameof(Levels.國小)); //顯示"國小" Console.WriteLine((char)Levels.大學); //顯示"D" Console.Read(); }

# by ByTIM

TO Jeffrey: 您提供的方法,看起來不錯,我找個需要的地方用用看,最後謝謝您。

Post a comment