最近在利用這個枚舉做一些狀態管理,遇到了int強轉枚舉的情況。但有時候內部情況還是不太清楚,因此,研究了這個強轉的過程,以及標記了Flags的枚舉的強轉。
一、Enum基本特性:
- 定義枚舉時,若不顯式指定值,則默認爲從0開始
- 當指定了某枚舉項的值,而緊隨其後的新項又不指定值,則該新項的值默認爲前一項的值+1
- Enum項的值可以重複。例如定義兩個項的值都顯式指定爲=1,是允許的。
- Enum的元素不能重複。
- int轉Enum遇到重複值時,優先匹配按名稱排序最前面的值。
舉例:
public enum expEnum
{
p0, //首項不指定值,則默認 =0
p1, //不指定值,則默認爲上一項 +1,此處 =1
p2, //同上,默認 +1=2
q1=1 //值可以重複,此處顯式指定 q1=1
q2, //同上,默認 +1=2
q1=2, //error: 類型已包含 q1 的定義
a1=1,
}
……
int status=1;
expEnum StatusEnum=(expEnum)status;
// 此時可以看到 StatusEnum==expEnum.a1 依字幕排序,a1 是排在 p1 和q1 前面,所以優先匹配。
二、帶Flags標記的 Enum
(1)基本特性:
- 表示該枚舉值,是可以進行位運算的
- 一般而言,添加了這個標記,枚舉則定義爲2的正整數冪,例如:1,2,4...。但這個是手動保證的,並不強制限定。
- 帶了Flags標記的Enum依然遵守上述Enum的所有基本特性,並不特殊。枚舉定義時,不指定首項的值時,首項依然默認是從0開始。
- 從int轉換爲Enum時,可以自動轉爲枚舉值的疊加。例如,6可以轉換爲4+2。不添加Flags則不能疊加,保留該int值。
舉例:
[Flags]
public enum expFlagsEnum
{
p0,//p0=0, 首項不指定值,依然是從 0 開始
p1,//p1=1, 默認 +1
p2,//p2=2
p4=4,
p8=8,
}
……
int status=7;
expFlagsEnum StatusEnum=(expFlagsEnum)status;
// 轉換後 可以看到 StatusEnum==expFlagsEnum.p1|expFlagsEnum.p2|expFlagsEnum.p4
……
//[Flags]---------------------------------去掉 Flags 標記
public enum expFlagsEnum
{
p0,//p0=0, 首項不指定值,依然是從0開始
p1,//p1=1, 默認+1
p2,//p2=2
p4=4,
p8=8,
}
……
int status=7;
expFlagsEnum StatusEnum=(expFlagsEnum)status;
// 轉換後 可以看到 StatusEnum==7
……
(2)特殊情形:非2正整數冪的枚舉值
網上的資料一般都不涉及這種特殊情形,只討論是2的整數冪即:1,2,4的情形,而不討論非2的整數冪的情況。
這裏的特殊主要特殊在從int轉Enum的時候,比較複雜。有這麼幾個原則:
- 最大值優先。
- int值按位拆分,再重組,以匹配枚舉項。拆分後的枚舉項,用二進制表示時,每一位上的1不能有重疊。(實際上就是按位或的逆運算)
舉例:
1、最大值優先
//================最大匹配原則================
[Flags]
public enum expFlagsEnum
{
p0,//p0=0, 首項不指定值,依然是從 0 開始
p1,//p1=1, 默認 +1
p2,//p2=2
p4=4,
p8=8,
p16=16,
p31=31,//非 2 的整數冪
p32=32
}
……
int status=47;
expFlagsEnum StatusEnum=(expFlagsEnum)status;
// 轉換後 可以看到 StatusEnum==expFlagsEnum.p1|expFlagsEnum.p2|expFlagsEnum.p4|expFlagsEnum.p8|expFlagsEnum.p32
……
//47 有多種匹配方式,例如:
//47 = 32+8+4+2+1
//47 = 31+16
//根據最大匹配原則,優先匹配的是包含 32 的方案,儘管 31+16 看上去更簡潔
2、按位拆分
//================最大匹配原則================
[Flags]
public enum expFlagsEnum
{
p0,//p0=0, 首項不指定值,依然是從 0 開始
p1,//p1=1, 默認 +1
p2,//p2=2
p3,
p4=4,
p8=8,
}
……
int status=6;
expFlagsEnum StatusEnum=(expFlagsEnum)status;
// 轉換後 可以看到 StatusEnum==6 即沒有轉爲枚舉值,直接保留了 int
……
//直覺上,應該是 6=3+2+1
// 但實際上6表示爲二進制即爲 110,即表示爲 6=100+010,於是6只能匹配 4+2 或者6自身
// 再舉例
int status=7;
expFlagsEnum StatusEnum=(expFlagsEnum)status;
// 轉換後 可以看到 StatusEnum==expFlagsEnum.p4|expFlagsEnum.p3
//拆分後的兩個數字,在轉換爲“二進制”時,每一位上不能有重疊。
// 7=111 可以分成 100和011 這兩個數字在每一位上的 1 不重疊,於是7 可以重組成4+3
// 至於爲什麼是 4+3而不是 4+2+1 上面已述,是最大值原則。
//
可以看到這樣的枚舉定義爲Flags情況會比較複雜,因此上,一般而言,標註了Flags,其項都是用2的整數冪,這樣是比較清晰的定義。
而一個複雜用法的例子就是Keys,標記了Flags,主要是爲了任意鍵和Ctrl,Alt,Shift的組合。這三個鍵定義的是2的整數冪,而且是大整數,以保證不會有其他枚舉項與之有重疊。
至於其他鍵,則不是很建議將其合併處理。合起來常常會產生衝突。
比如:A=65,B=66 按位或起來是67,成了Keys.C=67鍵了。
namespace System.Windows.Forms
{
[System.Flags]
public enum Keys
{
....
}
}