首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 設計模式:Enum vs Enum Class (類型安全枚舉) 深度對比與最佳實踐

     編輯:關於C語言

前言

表示「有限封閉值域」的領域概念(國家代碼、訂單狀態、貨幣類型)是領域建模核心。C# 內建 enum 提供編譯期型別安全,但受限於底層必為整數、無法攜帶行為/屬性、無法本地化顯示。Java 社群推廣的 Type-safe Enum Class (枚舉類) 模式以類別封裝常量實例,突破了這些限制。本文深度對比兩種模式在 C# 中的實現、序列化陷阱、操作符重載解法、擴展能力、適用場景,並給出決策指南。

內建 enum:優勢與限制


public enum Sex { Male, Female }
public enum CountryCode : short { US = 1, CN = 86, JP = 81 } // 指定底層型別
優勢限制
值類型,零堆配置,效能極佳底層必為整數 (sbyte/byte/short/ushort/int/uint/long/ulong)
編譯期型別安全,switch 完整性檢查無法附加屬性/方法 (顯示名、代碼、排序權重...)
語法簡潔,IL 直接支援無法本地化顯示 (ToString 只能輸出名稱)
FlagsAttribute 支援位組合無法分組/分層 (如亞洲國家子集)
序列化原生支援,無驚喜無法實現型別轉換邏輯 (Parse/TryParse 需外部)

Enum Class:核心實現


[Serializable]
public sealed class Sex {
    // 私有建構函數:確保值域封閉
    private Sex(string name, string displayName) {
        Name = name;
        DisplayName = displayName;
    }

    // 唯一靜態實例
    public static readonly Sex Male = new Sex("Male", "男");
    public static readonly Sex Female = new Sex("Female", "女");

    // 屬性
    public string Name { get; }
    public string DisplayName { get; }

    // 序列化安全:重載 ==/!= + 覆寫 Equals/GetHashCode
    public static bool operator ==(Sex left, Sex right) {
        if (ReferenceEquals(left, null)) return ReferenceEquals(right, null);
        return left.Equals(right);
    }
    public static bool operator !=(Sex left, Sex right) => !(left == right);

    public override bool Equals(object obj) {
        return obj is Sex other && Name == other.Name;
    }
    public override int GetHashCode() => Name.GetHashCode();

    // 友好顯示
    public override string ToString() => DisplayName; // 或依 CultureInfo 本地化

    // 型別轉換
    public static Sex Parse(string name) {
        return name switch {
            "Male" => Male,
            "Female" => Female,
            _ => throw new ArgumentException($"Unknown sex: {name}")
        };
    }
    public string ToDbValue() => Name; // 持久化值
}

序列化陷阱與操作符重載解法

問題


var formatter = new BinaryFormatter();
var stream = new MemoryStream();
formatter.Serialize(stream, Sex.Male);
stream.Position = 0;
Sex deserialized = (Sex)formatter.Deserialize(stream);
Console.WriteLine(deserialized == Sex.Male); // false! 反序列化產生新實例
  • 預設參考相等 → 反序列化破壞單例語義
  • Java 需 readResolve,C# 無此機制

解法:值相等語義

如上代碼:operator == 委派給 Equals,Equals 比較 Name (不變識別鍵)。要求:

  1. 每個實例擁有唯一不變識別鍵 (Name/Code)
  2. 覆寫 Equals/GetHashCode 保持一致
  3. 實現 ==/!= 供 C# 使用者直觀比較
  4. 標記 [Serializable]

Enum Class 擴展能力展示


public sealed class Country {
    private Country(string code, string name, string region, string lang) {
        Code = code; Name = name; Region = region; Lang = lang;
    }
    public string Code { get; }
    public string Name { get; }
    public string Region { get; }      // 分組屬性
    public string Lang { get; }        // 附加屬性

    public static readonly Country US = new Country("US", "United States", "NorthAmerica", "en");
    public static readonly Country CN = new Country("CN", "中國", "Asia", "zh-CN");
    public static readonly Country JP = new Country("JP", "日本", "Asia", "ja");

    // 分組查詢
    public static IEnumerable GetByRegion(string region) =>
        All.Where(c => c.Region == region);

    // 靜態集合支援 LINQ
    public static IEnumerable All => new[] { US, CN, JP };

    // 本地化顯示
    public string GetDisplayName(CultureInfo culture) =>
        culture.TwoLetterISOLanguageName switch {
            "zh" => Name, // 假設 Name 已是中文
            _ => Name
        };

    // ... operator ==, Equals, Parse, ToDbValue 同 Sex
}

Struct-based Enum (值類型枚舉類)

若需值類型語義 (堆疊分配、無 null、複製語義) 且值域非嚴格封閉 (允許使用者自訂值),可用 struct:


public readonly struct Color {
    public static readonly Color Red = new Color(0xFF0000, "Red");
    public static readonly Color Green = new Color(0x00FF00, "Green");
    public static readonly Color Blue = new Color(0x0000FF, "Blue");

    public int Rgb { get; }
    public string Name { get; }

    public Color(int rgb, string name) { Rgb = rgb; Name = name; }

    // 允許自訂值
    public static Color FromRgb(int rgb) => new Color(rgb, $"Custom#{rgb:X6}");

    // 運算子重載支援值比較
    public static bool operator ==(Color a, Color b) => a.Rgb == b.Rgb;
    public static bool operator !=(Color a, Color b) => a.Rgb != b.Rgb;
    // ... Equals/GetHashCode
}

差異:struct 必須有無參數建構函數 → 無法強制封閉值域;適合「預設值集合 + 開放擴展」場景 (如 System.Drawing.Color)。

決策指南:Enum 還是 Enum Class?

場景推薦理由
純內部狀態機、無顯示需求、效能極敏感enum零開銷、編譯期最佳化、switch 完整性
需本地化顯示名、需附加屬性 (代碼、排序、圖示)Enum Class封裝豐富資料、ToString 多態、屬性擴展
需分組/分層查詢 (如地區→國家)Enum Class靜態集合 + LINQ
需型別轉換邏輯 (Parse/TryParse/資料庫值)Enum Class封裝轉換規則、單一責任
跨進程/分散式序列化傳遞Enum Class (+ operator ==)值相等語義存活反序列化
位旗標組合 (Read/Write/Execute)enum + [Flags]原生位運算、IDE 支援
預設值集 + 開放自訂 (顏色、字體大小)readonly struct值語義 + 擴展性

現代 C# 擴展:Source Generator 自動生成 Enum Class

定義來源資料 (JSON/YAML/CSV) → Source Generator 產生 partial class Country { ... } → 開發者補充業務邏輯。實現「資料驅動枚舉」,避免手寫樣板代碼。

總結

enum 是輕量級值類型枚舉,適合單純狀態表示;Enum Class 是功能豐富的參考類型枚舉,適合領域模型中攜帶行為、屬性、本地化、轉換邏輯的核心概念。關鍵技術點:私有建構函數封閉值域、靜態 readonly 實例、操作符重載解決序列化身分識別、覆寫 Equals/GetHashCode/ToString、Parse/ToDbValue 封裝轉換。readonly struct 提供第三種選擇:值語義 + 開放擴展。根據領域複雜度、序列化需求、效能預算、團隊慣例選擇最適模式。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved