最近在利用这个枚举做一些状态管理,遇到了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
{
....
}
}