目录
// 引用声明:本文部分文字内容参考自 Solis, D.M., Illustrated C# 2012(Fourth Edition). 2013: 人民邮电出版社.
// 引用声明:本文图片转载自菜鸟社区,部分代码参考自Enumeration types (C# Programming Guide)
// 版权声明:支持原创,转载引用链接:https://blog.csdn.net/img_Guo/article/details/81870706
一、枚举是干什么的?
枚举和结构一样,是程序员自己定义的值类型。对比数值类型来说,枚举明确的为客户端代码指定对变量有效的值,枚举提供了一种有效的方式来定义可能分配给变量的一组已命名整数常量。
总之,枚举允许定义一个类型,提取我们提供的限定值集合中的一个值。
/* 代码功能:举个枚举的例子 —— 红绿灯三色
* 时间:2018年8月20日14点41分
*/
enum TrafficLightColor
{
Red, //枚举声明列表用逗号分隔
Green, //枚举声明中没有分号
Yellow
}
二、枚举的使用
2.1 枚举使用要求
1. 枚举是值类型,与结构一样,不使用引用分开存储成数据和引用,直接存储它们的数据;
2. 枚举只有一种成员:命名的整数值常量。因此,没有枚举类型变量时也可以像访问常量一样直接使用枚举名访问,但是不能对枚举成员使用任何的修饰符,枚举成员默认具有和枚举相同的访问性。
3. 因为枚举的成员是整数值常量,所以每个枚举成员都被赋予一个底层类型(默认为int类型)的常量值。而且在默认情况下,编译器把第一个成员设置为0,并对后续每个成员赋的值都比前一个大1。
4. 不能比较两个不同枚举类型的成员,相同枚举类型的成员可以进行比较。
/* 代码功能:枚举之间的比较
* 时间:2018年8月20日16点25分
*/
enum First
{
number1,
number2
}
enum Second
{
number1,
number2
}
class Program
{
static void Main()
{
if (First.number1 < First.number2) //OK,相同枚举类型之间可以比较
{ }
if (First.number1 < Second.number1) //error,不同枚举类型之间不能比较
{ }
}
}
5. .NET Enum 类型(Enum 就是基于该类型)还有一些静态方法:
(1)GetName:以枚举类型对象和整数为参数,返回响应的枚举成员的名称;
(2)GetNames:以枚举类型对象为参数,返回枚举中所有成员全部名称。
/* 代码功能:枚举中的静态方法
* 时间:2018年8月20日16点34分
*/
enum TrafficLightColor
{
Red,
Green,
Yellow
}
class Program
{
static void Main()
{
//这里使用typreof()运算符获取枚举类型对象
Console.WriteLine("交通灯枚举的第二个灯是:{0}\n",Enum.GetName(typeof(TrafficLightColor),1));
foreach (var i in Enum.GetNames(typeof(TrafficLightColor)))
Console.WriteLine(i);
}
}
/* 程序运行结果
---------------------------------------
交通灯枚举的第二个灯是:Green
Red
Green
Yellow
---------------------------------------
*/
2.2 枚举赋值说明
枚举类型赋值分为两种,一种是把枚举值字面量赋值给枚举类型变量,另一种是从另一个相同类型变量赋值。
/* 代码功能:枚举赋值说明
* 时间:2018年8月20日15点21分
*/
enum TrafficLightColor
{
Red, // 0 ,这是第一项
Yellow, // 1 ,比前一项大1
Green // 2 ,比前一项大1
}
class Program
{
static void Main()
{
//从成员赋值
TrafficLightColor f1 = TrafficLightColor.Red;
TrafficLightColor f2 = TrafficLightColor.Green;
//从变量赋值
TrafficLightColor f3 = f2;
//输出赋值和其整数值常量
Console.WriteLine("f1: 枚举值量:{0}, 枚举整数值常量:{1}", f1, (int)f1);
Console.WriteLine("f2: 枚举值量:{0}, 枚举整数值常量:{1}", f2, (int)f2);
Console.WriteLine("f3: 枚举值量:{0}, 枚举整数值常量:{1}",f3,(int)f3);
}
}
/* 程序运行结果
---------------------------------------
f1: 枚举值量:Red, 枚举整数值常量:0
f2: 枚举值量:Green, 枚举整数值常量:2
f3: 枚举值量:Green, 枚举整数值常量:2
---------------------------------------
*/
2.3 设置底层类型和显式值
可以把冒号和类型名放在枚举名后面,这样就可以使用其余任意的整数类型,并且所有常量值都属于枚举的这个整数类型。成员常量可以是底层类型的任何值。要显示设置成员值,需要在枚举声明中的变量名之后使用初始化表达式。等价的枚举声明如下:
/* 代码功能:等价枚举声明(设置底层类型、显式声明枚举)
* 时间:2018年8月20日15点44分
*/
enum TrafficLightColor
{
Red,
Yellow,
Green
}
/* 与上面相同的等价声明 */
enum TrafficLightColor : int //这里可以是任何整数类型
{
Red = 0, // 显示设置值
Yellow = 1, // 显示设置值
Green = 2 // 显示设置值
}
2.4 枚举可以设置重复值
枚举不可以有重复的名称,但是可以有重复的值。
/* 代码功能:枚举可以设置重复值,不可以设置重复的名称
* 时间:2018年8月20日15点59分
*/
enum TrafficLightColor
{
Red = 0, // 显示设置值
Yellow = 0, // 设置重复值,OK
Green = 1
Green = 2; //error,枚举不允许重复的名称
}
2.5 枚举成员编号
前面我们提到“在默认情况下,编译器把第一个成员设置为0,并对后续每个成员赋的值都比前一个大1”,这便是隐式成员的编号。
然而成员常量名称的值不需要是独特的,枚举允许显式赋值给任何成员常量,其余成员接受隐式编号。
/* 代码功能:枚举成员编号解释
* 时间:2018年8月20日16点15分
*/
enum Student
{
Tom, //0 默认隐式编号
Jack = 10, //10 显式设置编号
Amy, //11 默认隐式编号
Smith, //12
Brown = 5, //5 显示设置编号
Johnson, //6
Williams = Brown, //5 以上定义了Brown
Jones //6
}
三、位标志
本人学习位标志的时候。花费了很大力气,尤其是以开始看书的时候不能看懂什么是按位逻辑运算符,导致了理解位操作的位标志的时候非常困难。因此在介绍位标志之前,我们先介绍一下什么是逻辑运算符。
3.1 逻辑运算符
逻辑运算符是为整型类型和bool预定义的运算符,其中按位逻辑运算符经常用于设置位组的方法参数,除了按位非运算符是一元运算符以外和按位与运算符同时可以作二元运算符、一元运算符(取地址符)以外,都是二元左结合运算符。不多介绍这些概念,我们直接看怎么用:
1、 | 运算符:针对整型类型,它会产生两个操作数的按位OR,即只要任何一个操作位为1结果位就是1;针对bool操作数,它会计算其操作数的逻辑OR,即两个bool型都是false时结果才为false。
2、& 运算符:针对整型类型,它会产生两个操作数的按位AND,即仅当两个操作位都为1结果位才是1;针对bool操作数,它会计算其操作数的逻辑AND,即两个bool型都是true时结果才是true。
3、 ^ 运算符:针对整形类型,它会产生两个操作数的按位异或,即仅当一个(而不是两个)操作位为1时结果位才为1;针对bool操作数,它会计算操作数的逻辑异或,即仅当一个(而不是两个)操作数为true时结果才是true。
4、 ~ 运算符:一元运算符,对操作数的每个位都取反。即每个0都变为1,每个1都变成0;
值得注意:二元 &
运算符计算两个运算符,无论第一个运算符的值是什么,这和条件逻辑运算符&&相反,逻辑运算符&&采用“短路”模式,如果第一个运算符值是false,那么第二个运算符就不计算了。
3.2 位标志是什么?
简单的说,位标志是使枚举类型的实例能够存储枚举器列表中定义的值的任何组合。
在编程的时候,开发人员长期使用单个字的不同位作为表示一组开/关标志的紧凑方法,我们称这个单个字为标志字。
3.3 Flags特性
想要使用位标志,就需要在枚举声明前使用Flags特性,它提供以下功能:
1. 首先,Flags会告诉编译器等查看代码的工具,该枚举的成员不仅可以用作单独的值,还可以按位标志进行组合,这样编译器就可以适当的解释该枚举类型的变量;
2. 其次,它允许枚举的ToString方法为位标志得到值提供更多的格式化信息。ToString方法以一个枚举值为参数,将枚举值与常量成员进行比较,如果与某个成员相匹配,Tostring返回该成员的字符串名称。下面举例两段代码进行解释:
/* 代码功能:不使用Flags位标志特性,和下面使用Flags位标志的代码进行对比
* 时间:2018年8月20日22点53分
*/
enum WorkDays
{
Monday = 0x1, // 位0
Tuesday = 0x2, // 位1
Wednesday = 0x4, // 位2
Thursday = 0x8, // 位3
Friday = 0x10, // 位4
}
class Program
{
static void Main()
{
WorkDays ops = WorkDays.Monday; //设置一个位标志
Console.WriteLine(ops.ToString()); //利用Tostring输出位标志
ops = WorkDays.Tuesday | WorkDays.Wednesday; //设置两个位标志
Console.WriteLine(ops.ToString());
}
}
/* 程序运行结果
---------------------------------------
Monday
6
---------------------------------------
*/
【问】:这里为什么会输出6呢?
【答】:这里显示的6是ops的值,当使用位或运算符设置Tuesday和Thursday的值给ops,最终ops得到的值是6。当ToString()方法查询哪个枚举成员具有值6,没有找打,只能输出它的值6。
这个问题可以通过Flags特性解决,如果使用Flags特性,编译器会告诉ToString()位可以分开考虑,查找值的时候,也会组合它们并返回它们的名称。
/* 代码功能:使用位标志特性,和上面不使Flags位标志特性的代码进行对比
* 时间:2018年8月20日23点01分
*/
[Flags] //这里添加了位标志特性
enum WorkDays
{
Monday = 0x1, // 位0
Tuesday = 0x2, // 位1
Wednesday = 0x4, // 位2
Thursday = 0x8, // 位3
Friday = 0x10, // 位4
}
class Program
{
static void Main()
{
WorkDays ops = WorkDays.Monday; //设置一个位标志
Console.WriteLine(ops.ToString()); //利用Tostring输出位标志
ops = WorkDays.Tuesday | WorkDays.Wednesday; //设置两个位标志
Console.WriteLine(ops.ToString());
}
}
/* 程序运行结果
---------------------------------------
Monday
Tuesday, Wednesday
---------------------------------------
*/
可以看到,这次使用了位标志,ToString()方法会进行组合查询并输出它们的枚举成员字面值。
3.4 位标志的使用
一般使用步骤:
1. 确定需要多少个位标志,并选择一种足够多位的无符号类型保存它;
2. 确定每个位位置代表什么,并给它们一个名称。声明一个选中的整数类型的枚举,每个成员由一个位标志代替;
3. 应用System.FlagsAttribute属性,并适当定义一些值,以便对这些值执行按位逻辑运算符操作,为了能够进行组合,最好将它的每个底层值都是 2 的若干次幂,指数依次递增;
4. 使用按位或(OR)运算符设置保持该位标志字中适当的位;
5. 使用按位与(AND)运算符,或者使用HasFlag方法解开位标志。
注意:在位标志枚举中,因为一个位标志表示一个或开或关的位,这其中包括一个值为0(表示“未设置任何标志”)的命名常量,它有一个意思:所有位标志都是关的。 如果在编程的时候不想让零值表示“未设置任何标志”,请勿为标志指定零值。
/* 代码功能:位标志的使用
* 时间:2018年8月20日22点36分
*/
[Flags]
enum WorkDays
{
None = 0x0, // 零值,表示所有位标志都是关闭状态
Monday = 0x1, // 底层值设置为2的若干次幂,依次递增
Tuesday = 0x2,
Wednesday = 0x4,
Thursday = 0x8,
Friday = 0x10,
}
class MyClass
{
bool UseMonday = false,
UseTuesday = false,
UseWednesday = false,
UseThursday = false,
UseFriday = false,
UseMondayAndTuesday = false;
//设置是否包含位选项并返回bool类型值
public void SetOptions(WorkDays ops)
{
UseMonday = ops.HasFlag(WorkDays.Monday);
UseTuesday = ops.HasFlag(WorkDays.Tuesday);
UseWednesday = ops.HasFlag(WorkDays.Wednesday);
UseThursday = ops.HasFlag(WorkDays.Thursday);
UseFriday = ops.HasFlag(WorkDays.Friday);
}
//输出各个位的bool类型
public void PrintOptions()
{
Console.WriteLine("Options settings:");
Console.WriteLine(" Use Monday - {0}", UseMonday);
Console.WriteLine(" Use Tuesday - {0}", UseTuesday);
Console.WriteLine(" Use Wednesday - {0}", UseWednesday);
Console.WriteLine(" Use Thursday - {0}", UseThursday);
Console.WriteLine(" Use Friday - {0}", UseFriday);
Console.WriteLine(" Use Monday and Tuesday - {0}\n", UseMondayAndTuesday);
}
}
class Program
{
static void Main()
{
MyClass f1 = new MyClass();
//使用位或初始化两个标志
Console.WriteLine("使用位或初始化星期一,二两个位标志:");
WorkDays ops = WorkDays.Monday | WorkDays.Tuesday;
f1.SetOptions(ops);
f1.PrintOptions();
//使用位或添加额外的标志
Console.WriteLine("使用位或添加星期三这个位标志:");
ops = ops | WorkDays.Wednesday;
f1.SetOptions(ops);
f1.PrintOptions();
//使用位异或删除一个标志
Console.WriteLine("使用位或删除星期一这个位标志:");
ops = ops ^ WorkDays.Monday;
f1.SetOptions(ops);
f1.PrintOptions();
}
}
/* 程序运行结果
---------------------------------------
使用位或初始化星期一,二两个位标志:
Options settings:
Use Monday - True
Use Tuesday - True
Use Wednesday - False
Use Thursday - False
Use Friday - False
Use Monday and Tuesday - False
使用位或添加星期三这个位标志:
Options settings:
Use Monday - True
Use Tuesday - True
Use Wednesday - True
Use Thursday - False
Use Friday - False
Use Monday and Tuesday - False
使用位或删除星期一这个位标志:
Options settings:
Use Monday - False
Use Tuesday - True
Use Wednesday - True
Use Thursday - False
Use Friday - False
Use Monday and Tuesday - False
---------------------------------------
*/
3.5 判断标志字是否设定指定的标志集
有两种判断方法,一是使用Enum类型中的HasFlag()布尔方法,二是使用按位与运算符方法,其中HasFlag()布尔方法可以判断多个位标志,而按位与运算符方法习惯上只判断一个位标志。
3.5.1 Enum类型中的HasFlag()布尔方法
/* 代码功能:HasFlag()方法使用
* 时间:2018年8月20日23点23分
*/
[Flags]
enum WorkDays
{
Monday = 0x1, // 位0
Tuesday = 0x2, // 位1
Wednesday = 0x4, // 位2
Thursday = 0x8, // 位3
Friday = 0x10, // 位4
}
class Program
{
static void Main()
{
WorkDays ops = WorkDays.Monday | WorkDays.Tuesday | WorkDays.Wednesday;
//HasFlag()检查单个位标志
bool UseTuesday = ops.HasFlag(WorkDays.Tuesday);
Console.WriteLine("是否包含Tuesday标志? {0}", UseTuesday);
//HasFlag()检查多个位标志
WorkDays test = WorkDays.Monday | WorkDays.Wednesday;
bool UseMondayAndWednesday = ops.HasFlag(test);
Console.WriteLine("是否包含Monday and Wednesday标志? {0}", UseMondayAndWednesday);
}
}
/* 程序运行结果
---------------------------------------
是否包含Tuesday标志? True
是否包含Monday and Wednesday标志? True
---------------------------------------
*/
3.5.2 按位与运算符方法
通过把标志字ops与位标志相与,然后与位标志比较结果,如果原始值中设置了这个位,那么与操作的结果将和位标志有相同的位模式,结果返回True。
/* 代码功能:HasFlag()方法使用
* 时间:2018年8月20日23点34分
*/
[Flags]
enum WorkDays
{
Monday = 0x1, // 位0
Tuesday = 0x2, // 位1
Wednesday = 0x4, // 位2
Thursday = 0x8, // 位3
Friday = 0x10, // 位4
}
class Program
{
static void Main()
{
WorkDays ops = WorkDays.Monday | WorkDays.Tuesday | WorkDays.Wednesday;
//按位与运算符方法
bool UseTuesday =
(ops & WorkDays.Tuesday) == WorkDays.Tuesday;
Console.WriteLine("是否包含Tuesday标志? {0}", UseTuesday);
}
}
/* 程序运行结果
---------------------------------------
是否包含Tuesday标志? True
---------------------------------------
*/