摘要:.NET中的枚举分为简单枚举和标志枚举,这次主要总结一下标志枚举适用条件,以及它的使用方法,并在文章的最后列举枚举使用的一些规范。
在刚接触.NET的枚举时,只用简单的枚举,对于标记枚举,只知道是在枚举类型加上Flags
特性,然后给枚举值赋予十六进制的值,并且书中还特别明确规定值必须是以2的指数才可以,这样可以方便在使用时,对多个枚举值进行或运算。随着对.NET的了解不断的深入,现在终于明白了标志枚举的适用场合以及它的使用方法,因此在这里总结一下。
标志枚举的适用条件
在System.Drawing
命令空间中有一个FontStyle
标志枚举,专门用来设置字体的风格,有Blod、Italic、Regular、Strikeout、Underline这5种风格。当然我们需要设置字体为单个一种风格时,那么FontStyle枚举的用法和简单枚举没什么区别,比如,我们需要把字体风格为设置为粗体,代码如下:
System.Drawing.Font font = new System.Drawing.Font("宋体",12,FontStyle.Bold);
但是,如果我们需要设置字体的风格为粗体的同时还要需要斜体和下划线呢?对于这种情况,简单枚举就不适用了,这就必须使用标志枚举的进行组合才能办到了,代码如下:
System.Drawing.Font font = new System.Drawing.Font("宋体",12, FontStyle.Bold|FontStyle.Italic|FontStyle.Underline);
通过这样的组合就能够达到我们想要的效果。
自定义FontStyle标志枚举,一探究竟
下面我们自己设计一个FontStyle
,看看它的内部是如何实现的。代码如下:
[Flags]
public enum FontStyle: byte
{
Bold=0x01,
Italic = 0x02,
Regular = 0x04,
Strikeout=0x08,
Underline=0x10,
}
上面的定义了一个FontStyle
枚举,该枚举继承byte
类型,通过加上Flags
特性,使它成为标志枚举,并且为每一个枚举值赋值一个十六进制的值,每一个值都是2的指数值。下面将详细解释一些为什么要使用2的指数来进行赋值。
为什么使用2的指数值对枚举进行赋值
首先我们要明白,二进制的或运算,或运行无非就4种情况,
0|0=0; 0|1=1; 1|0=1; 1|1=1;
那么二进制10101010和01010101进行或运算就等于11111111,明白了二进制的或运算,再仔细想想,一个字节8位二进制,可以代表0-255。其中1,2,4,8,16,32,64,128是2的指数,分别代表的二进制的00000001,00000010,00000100,00001000,00010000,00100000,01000000,10000000,它们的每个取值刚好是将8位中的某一位设为1。那么这8个值以及它们任意组合(或运算)刚好能够代表$C1_8+C2_8+C3_8+C4_8+C5_8+C6_8+C7_8+C8_8=255 $种情况(其中$C_8^1$是单个枚举,其他都是组合),对标志枚举使用2的指数值进行赋值,能够进行任意的组合,并且任意组合后的值不会有重复。
这就是为什么要使用2的指数值对标志枚举进行复制的原因。顺便这里再多说一句,赋值的时候为什么不用10进制,而是用16进制的表示法,前面我们分析了,标志枚举的组合最后都将进行二进制的或运算,而我们在赋值的时候用16进制会更加表现明显,因为每一个16进制的数值,刚好代表二进制4个位,所以两个16进制的数就能代表一个字节,比如0x11,我们化为二进制,只需要对两个1分别换算4个二进制位就行了,1=0001,所以0x11就代表00010001,如果用十进制17来表示,就没那么轻易地看出它的二进制表示。
如何处理组合枚举
当我们把标志枚举赋值为2的指数值之后,我们就可以组合的使用这些枚举值,以期达到我们想要的效果,那么在函数设计中,我们是如何来处理传入进来的枚举组合值呢?其实也非常简单,正是因为他们任意的组合都不会重复,因此每一个数值可以代表其中的一个枚举值或者是一个组合,比如,FontStyle.Bold|FontStyle.Italic|FontStyle.Underline
这个组合所代表的数值就位0x01|0x02|0x10=19.
下面设计一个函数处理传入进来的枚举值。代码如下:
static void Test(FontStyle fontStyle)
{
switch ((byte)fontStyle)
{
case 1:
Console.WriteLine("Bold");
break;
case 2:
Console.WriteLine("Italic");
break;
case 3:
Console.WriteLine("Bold&Italic");
break;
case 4:
Console.WriteLine("Regular");
break;
case 6:
Console.WriteLine("Regular&Italic");
break;
case 8:
Console.WriteLine("Strikeout");
break;
case 9:
Console.WriteLine("Strikeout&Bold");
break;
case 10:
Console.WriteLine("Strikeout&Italic");
break;
case 11:
Console.WriteLine("Strikeout&Italic&Bold");
break;
case 16:
Console.WriteLine("Underline");
break;
case 19:
Console.WriteLine("Bold&Italic&Underline");
break;
//else
}
}
因为枚举在CLR内部实现可以看做是数值类型。在Test函数内部我们需要用Switch去判断传入的枚举值,确定是否单个枚举还是组合后的值,并对其做出相应的处理。下面给出测试代码。
static void Main(string[] args)
{
Test(FontStyle.Bold);
Test(FontStyle.Strikeout|FontStyle.Italic);
Test(FontStyle.Bold|FontStyle.Italic|FontStyle.Underline);
Console.ReadKey();
}
测试结果如下:
总结一些设计枚举的规范
- 简单枚举的命名采用单数形式。
- 不要使用Enum、Flag、Flags作为枚举类型的后缀。
- 不要给枚举值的命名加上枚举类型名前缀,比如
FontStyleBlod
这样是不规范的。 - 使用枚举代替一些固定的常量集合。
- 使用枚举作为参数、属性、返回值,来保证该API是强类型的API。
- 考虑给枚举提供一个
None=0
的枚举值。 - 给标志枚举赋值为2的指数值。