淺談 Flag Enum 的應用與心得
TLDR
- Flag Enum 透過
[Flags]屬性與位元運算,能有效管理多重狀態組合。 - 定義時應使用 2 的 N 次方(如
1 << 0,1 << 1)確保旗標獨立。 - 建議使用
HasFlag方法進行狀態判斷,程式碼更具可讀性。 - 應避免使用 NOT (
~) 運算符定義複合列舉項目,以免產生非預期的數值結果。 - 建議 Flag Enum 型別名稱使用複數(如
Permissions),一般 Enum 使用單數(如DayOfWeek)。 - 若 Enum 可能會擴充項目,應謹慎定義並使用
All類型的組合值。
Flag Enum 的定義與設計規範
什麼情況下會遇到這個問題:當需要定義一組支援多重狀態組合的列舉,且希望程式碼具備高可讀性與擴充性時。
Flag Enum 透過 [Flags] 屬性標記,並利用位元運算來組合多個狀態。定義時,每個旗標必須是 2 的 N 次方,以確保每個位元都是獨立的。
csharp
[Flags]
enum Permissions {
None = 0,
CanQuery = 1 << 0, // 1
CanCreate = 1 << 1, // 2
CanUpdate = 1 << 2, // 4
CanDelete = 1 << 3 // 8
}命名與維護建議
- 命名規範:Microsoft 建議 Flag Enum 使用複數命名(如
Permissions),一般 Enum 使用單數命名(如DayOfWeek)。 - 擴充性考量:若定義了
All組合值,當未來新增列舉項目時,必須同步更新All的定義,否則會導致邏輯錯誤。 - 避免使用 NOT (
~):使用~定義複合值容易導致ToString()輸出為數值而非名稱,且在判斷包含關係時容易產生邏輯偏差,應盡量避免。
Flag Enum 的操作與判斷
什麼情況下會遇到這個問題:當需要對多重狀態進行聯集、交集、補集運算,或檢查某個狀態是否存在於組合中時。
位元運算應用
- OR (
|):用於合併狀態(聯集)。 - AND (
&):用於篩選狀態(交集)。 - XOR (
^):用於反轉狀態(對稱差集)。 - NOT (
~):用於排除狀態(補集)。
若要從組合中移除特定項目,建議使用以下方式:
csharp
// 移除 CanCreate
Permissions result = Permissions.CanUpsert & ~Permissions.CanCreate;狀態判斷
判斷是否包含特定旗標,建議優先使用 .HasFlag() 方法,其語意比手動進行位元運算更清晰。
csharp
// 使用 HasFlag 判斷
bool hasCreate = Permissions.CanUpsert.HasFlag(Permissions.CanCreate);
// 傳統位元運算判斷
bool hasCreate = (Permissions.CanUpsert & Permissions.CanCreate) == Permissions.CanCreate;關於 None 的判斷
即使定義了 None = 0,使用 HasFlag(Permissions.None) 永遠會回傳 true,因為空集合在邏輯上被視為任何集合的子集。
異動歷程
- 2023-12-05 初版文件建立。
