在 C# 中,列舉的底層型別只能是整數型別(byte、short、int、long 等),並不支援直接使用 string 作為成員值。然而,實際開發中常遇到需求:希望列舉成員能直接對應特定字串(如 HTML 顏色代碼 "#FF0000")。本文將示範如何透過類別模擬出「字串型別列舉」的行為,並探討其優缺點與 .NET Framework 內建的解決方案。
列舉的特性是「透過成員初始化,而非建構子」。因此將建構子設為 private,並暴露 static readonly 欄位作為預定義實例:
public class Color
{
private Color(string value) { m_Value = value; }
private string m_Value;
public static readonly Color Red = new Color("#FF0000");
public static readonly Color Green = new Color("#00FF00");
public static readonly Color Blue = new Color("#0000FF");
}
使用 readonly 而非 const,是因為 const 僅適用於編譯期常數,而這裡的實例是在執行期建立的。
重載 explicit operator 實現 Color ⇄ string 的強制轉換:
// Color → string
public static explicit operator string(Color value) => value.m_Value;
// string → Color
public static explicit operator Color(string value)
{
switch (value)
{
case "#FF0000": return Red;
case "#00FF00": return Green;
case "#0000FF": return Blue;
default: throw new InvalidCastException();
}
}
若傳入非預期字串,拋出 InvalidCastException 以保證型別安全。
public class Color
{
private Color(string value) { m_Value = value; }
private string m_Value;
public static explicit operator Color(string value) { /* ... */ }
public static explicit operator string(Color value) => m_Value;
public override string ToString() => m_Value;
public static readonly Color Red = new Color("#FF0000");
public static readonly Color Green = new Color("#00FF00");
public static readonly Color Blue = new Color("#0000FF");
}
關於等值比較:所有實例皆源自靜態唯讀欄位,因此參考相等(ReferenceEquals)即可滿足需求,無需額外重載 Equals 或 ==/!=。
此 Color 類別同時扮演「列舉標識」與「資料載體」兩個角色:既要提供像列舉一樣的 Red、Green 成員,又要攜帶 "#FF0000" 這類資料。這違反了 SRP,導致擴充性極差:若需加入 Alpha 通道、支援 CMYK 轉換、或處理上百萬種顏色,程式碼將難以維護。
.NET 在 System.Drawing 命名空間提供了三個協作的類型,職責劃分清晰:
Indigo、Desktop),不攜帶資料,比較極快。FromKnownColor/ToKnownColor 轉換,並正確實作等值比較。Color 與 HTML 顏色字串("#FF0000")之間的轉換(ToHtml/FromHtml)。使用範例:
string desktopColorHtml = ColorTranslator.ToHtml(Color.FromKnownColor(KnownColor.Desktop));
這種「列舉做標識、結構做資料、工具類做轉換」的分層設計,才是符合 SRP 且經得起時間考驗的架構。