c#中的 Int 强转标记了Flags的枚举Enum

最近在利用这个枚举做一些状态管理,遇到了int强转枚举的情况。但有时候内部情况还是不太清楚,因此,研究了这个强转的过程,以及标记了Flags的枚举的强转。

一、Enum基本特性:

  1. 定义枚举时,若不显式指定值,则默认为从0开始
  2. 当指定了某枚举项的值,而紧随其后的新项又不指定值,则该新项的值默认为前一项的值+1
  3. Enum项的值可以重复。例如定义两个项的值都显式指定为=1,是允许的。
  4. Enum的元素不能重复。
  5. 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)基本特性:

  1. 表示该枚举值,是可以进行位运算的
  2. 一般而言,添加了这个标记,枚举则定义为2的正整数幂,例如:1,2,4...。但这个是手动保证的,并不强制限定。
  3. 带了Flags标记的Enum依然遵守上述Enum的所有基本特性,并不特殊。枚举定义时,不指定首项的值时,首项依然默认是从0开始。
  4. 从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的时候,比较复杂。有这么几个原则:

  1. 最大值优先。
  2. 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
{
....
}
}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章