目录
一、枚举
枚举是由程序员定义的类型,与类或结构一样:
- 与结构一样,枚举是值类型,因此直接存储它们的数据,而不是分开存储成引用和数据;
- 枚举只有一种类型的成员:命名的整数常量;
下面的代码展示了一个示例,声明了一个名称为TrafficLight的新枚举类型,它含有3个成员。注意成员声明列表是逗号分隔的列表,在枚举声明中没有分号。
enum TrafficLight
{
Green, //逗号分隔,没有分号
Yellow, //逗号分隔,没有分号
Red
}
每个枚举类型都有一个底层整数类型,默认为int。
- 每个枚举成员都被赋予一个底层类型的常量值;
- 在默认情况下,编译器对第一个成员赋值为0,对每一个后续成员赋的值都比前一个成员多1;
例如,在TrafficLight类型中,编译器把int值0、1和2分别赋值给成员Green、Yellow和Red。在下面代码的输出中,把它们转换成类型int,可以看到底层的成员值。
enum TrafficLight
{
Green, //逗号分隔,没有分号
Yellow, //逗号分隔,没有分号
Red
}
class Program
{
static void Main(string[] args)
{
TrafficLight t1 = TrafficLight.Green;
TrafficLight t2 = TrafficLight.Yellow;
TrafficLight t3 = TrafficLight.Red;
Console.WriteLine($"{t1}, \t{(int)t1}");
Console.WriteLine($"{t2}, \t{(int)t2}");
Console.WriteLine($"{t3}, \t{(int)t3}");
}
}
执行结果:
可以把枚举值赋给枚举类型变量。
static void Main()
{
TrafficLight t1 = TrafficLight.Red; //从成员赋值
TrafficLight t2 = TrafficLight.Green; //从成员赋值
TrafficLight t3 = t2; //从变量赋值
Console.WriteLine(t1);
Console.WriteLine(t2);
Console.WriteLine(t3);
}
执行结果:
注意,可以把成员字面量赋给变量,或从另一个相同类型的变量复制值。
注意,成员名被当作字符串打印。
1、设置底层类型和显式值
可以把冒号和类型名放在枚举名之后,这样就可以使用int以外的整数类型。类型可以是任何整数类型,所有成员常量都属于枚举的底层类型。
enum TrafficLight : ulong //设置底层类型
{
...
成员常量的值可以是底层类型的任何值,要显式地设置一个成员的值,在枚举声明中的变量名之后使用初始化表达式。尽管不能有重复的名称,但可以有重复的值,如下所示:
enum TrafficLight
{
Green = 10,
Yellow = 15, //重复的值
Red = 15 //重复的值
}
2、隐式成员编号
可以显式地给任何成员常量赋值,如果不初始化成员常量,编译器将隐式地给它赋一个值。
关联到成员名称的值不需要是独特的。
enum CardSuit
{
Hearts, //0 因为这是第一项
Clubs, //1 比之前大1
Diamonds, //2 比之前大1,下面的以此类推
Spades,
MaxSuits
}
enum FaceCards
{
//Member //所赋的值
Jack =11, //11 显式设置
Queen, //12 比之前大1
King, //13 比之前大1
Ace, //14 比之前大1
NumberOfFaceCards=4, //4 显式设置
SomeOtherValue, //5 比之前大1
HighestFaceCard=Ace //14 以上定义了Ace
}
二、位标志
程序员们长期使用单个字的不同位作为表示一组开/关标志的紧凑方法,本节将其称为标志字,枚举提供了实现它的简便方法。
一般步骤如下:
- 确定需要多少个位标志,并选择一种有足够多位的无符号类型来保存它;
- 确定每个位位置代表什么,并给它们一个名称,声明一个选中的整数类型的枚举,每个成员由一个位位置表示;
- 使用按位或运算符在持有该位标志的字中设置适当的位;
- 使用按位与运算符或HasFlag方法检查是否设置了特定位标志;
[Flags]
enum CardDeckSettings:uint
{
SingleDeck =0x01, //位0
LargePictures =0x02, //位1
FancyNumbers =0x04, //位2
Animation =0x08 //位3
}
上述代码展示了枚举声明,表示纸牌游戏中一副牌的选项。底层类型uint足够满足4个位标志的需要了。注意代码的下列内容:
- 成员有表示二进制选项的名称
- 每个选项由字中一个特定的位位置表示,位位置持有一个0或一个1;
- 因为一个位标志表示一个或开或关的位,所以你不会想用0作为成员值,它已经有了一个含义:所有的位标志都是关;
- 在十六进制表示法中,每个十六进制数字用4位来表示,由于位模式和十六进制表示法之间的这种直接联系,在处理位模式时,经常使用十六进制而不是十进制表示法;
- 从C#7.0开始,可以使用二进制表示法了;
- 使用Flags特性装饰枚举实际上不是必要的,但可以带来一些额外的便利;
要创建一个带有适当的位标志的字,需要声明一个该枚举类型的变量,并使用按位或运算符设置需要的位。
CardDeckSettings ops= CardDeckSettings.SingleDeck
|CardDeckSettings.FancyNumbers
|CardDeckSettings.Animation;
要判断标志字是否包含特定的位标志集,可以使用枚举类型的HasFlag布尔方法,在标志字上调用HasFlag方法,并将要检查的位标志作为参数,如果设置了指定的位标志,HasFlag返回true,否则返回false。
bool useFancyNumbers=ops.HasFlag(CardDeckSettings.FancyNumbers);
HasFlag方法还可以检测多个位标志。
CardDeckSettings testFlags=CardDeckSettings.Animation|CardDeckSettings.FancyNumbers;
bool useAnimationAndFancyNumbers=ops.HasFlag(testFlags);
上述代码检查op标志字是否设置了Animation和FancyNumbers位,代码做了如下事情:
- 第一行语句创建了一个测试字实例,叫做testFlags,设置了Animation和FancyNumbers标志位;
- 然后把testFlag作为参数传给HasFlag方法;
HasFlag检测是否测试字中的所有标志都在ops标志字中进行了设置,如果是的话,HasFlag返回true,否则返回false。
另一种判断是否设置了一个或多个指定位的方法时使用按位与运算符。
bool useFancyNumbers=
(ops&CardDeckSettings.FancyNumbers)==CardDeckSettings.FancyNumbers;
1、Flags特性
前面的代码在枚举声明之前使用了Flags特性。
Flags特性不会改变计算结果,却提供了一些方便的特性。首先,它通知编译器、对象浏览器以及其他查看这段代码的工具,该枚举的成员不仅可以用作单独的值,还可以组合成位标志,这样浏览器就可以更恰当地解释该枚举类型的变量。
其次,它允许枚举的ToString方法为位标志的值提供更多的格式化信息。ToString方法以一个枚举值为参数,将其与枚举的常量成员相比较,如果与某个成员相匹配,ToString返回该成员的字符串名称。
enum CardDeckSettings : uint
{
SingleDeck = 0x01, //位0
LargePictures = 0x02, //位1
FancyNumbers = 0x04, //位2
Animation = 0x08 //位3
}
class _2_1FlagsAttribute
{
static void Main()
{
CardDeckSettings ops;
ops = CardDeckSettings.FancyNumbers; //设置一个标志
Console.WriteLine(ops.ToString());
ops = CardDeckSettings.FancyNumbers | CardDeckSettings.Animation; //设置两个标志
Console.WriteLine(ops.ToString());
}
}
执行结果:
在上述代码中,Main做了以下事情:
- 创建枚举类型CarDeckSettings的变量,设置一个位标志,并打印变量的值(即FancyNumbers);
- 为变量赋一个包含两个位标志的新值,并打印它的值(即12);
作为第二次赋值的结果而显式的值12是ops的值,它是一个int,因为FancyNumbers将位设置为4,Animation将位设置为8,因此最终得到int值12。在赋值语句之后的WriteLine方法中,ToString方法会查找哪个枚举成员具有值12,由于没有找到,因此会打印出12。
然而,如果在枚举声明前加上Flags特性,将告诉ToString方法位可以分开考虑,在查找值时,ToString会发现12对应两个分开的为标志成员——FancyNumbers和Animation,这时将返回它们的名称,用逗号和空格隔开。
[Flags]
enum CardDeckSettings : uint
{
SingleDeck = 0x01, //位0
LargePictures = 0x02, //位1
FancyNumbers = 0x04, //位2
Animation = 0x08 //位3
}
class _2_1FlagsAttribute
{
static void Main()
{
CardDeckSettings ops;
ops = CardDeckSettings.FancyNumbers; //设置一个标志
Console.WriteLine(ops.ToString());
ops = CardDeckSettings.FancyNumbers | CardDeckSettings.Animation; //设置两个标志
Console.WriteLine(ops.ToString());
}
}
执行结果:
2、使用位标志的示例
[Flags]
enum CardDeckSettings : uint
{
SingleDeck = 0x01, //位0
LargePictures = 0x02, //位1
FancyNumbers = 0x04, //位2
Animation = 0x08 //位3
}
class MyClass
{
bool UseSingleDeck = false,
UseBigPics = false,
UseFancyNumbers = false,
UseAnimation = false,
UseAnimationAndFancyNumbers = false;
public void SetOptions(CardDeckSettings ops)
{
UseSingleDeck = ops.HasFlag(CardDeckSettings.SingleDeck);
UseBigPics = ops.HasFlag(CardDeckSettings.LargePictures);
UseFancyNumbers = ops.HasFlag(CardDeckSettings.FancyNumbers);
UseAnimation = ops.HasFlag(CardDeckSettings.Animation);
CardDeckSettings testFlags = CardDeckSettings.Animation | CardDeckSettings.FancyNumbers;
UseAnimationAndFancyNumbers = ops.HasFlag(testFlags);
}
public void PrintOptions()
{
Console.WriteLine("Option settings:");
Console.WriteLine("Use Single Deck - {0}", UseSingleDeck);
Console.WriteLine("Use Large Pictures - {0}", UseBigPics);
Console.WriteLine("Use Fancy Numbers - {0}", UseFancyNumbers);
Console.WriteLine("Show Animation - {0}", UseAnimation);
Console.WriteLine("Show Animation And FancyNumbers - {0}", UseAnimationAndFancyNumbers);
}
}
class Program
{
static void Main()
{
var mc = new MyClass();
CardDeckSettings ops = CardDeckSettings.SingleDeck
| CardDeckSettings.FancyNumbers
| CardDeckSettings.Animation;
mc.SetOptions(ops);
mc.PrintOptions();
}
}
执行结果:
三、关于枚举的更多内容
枚举只有单一的成员类型:声明的成员常量。
- 不能对成员使用修饰符,他们都隐式地具有和枚举相同的可访问性;
- 由于成员是静态的,即使在没有该枚举类型的变量时也可以访问它们;
Console.WriteLine("{0}",TrafficLight.Green);
上述代码没有创建枚举TrafficLight类型的任何变量,但由于它的成员是静态的,所以是可访问的,并且可以使用WriteLine打印。
- 和所有的静态类型一样,访问枚举的成员有两种方法;
- 可以使用类型名称,后面跟着一个点和成员名;
- 从C#6.0开始,可以使用using static指令来避免在每次使用时都包含类名,这可以使代码更简洁;
枚举是一个独特的类型,比较不同枚举类型的成员会导致编译时错误。
enum FirstEnum //第一个枚举类型
{
Mem1,
Mem2
}
enum SecondEnum //第二个枚举类型
{
Mem1,
Mem2
}
class Program
{
static void Main()
{
if(FirstEnum.Mem1<FirstEnum.Mem2) //正确,枚举类型相同
Console.WriteLine("True");
if(FirstEnum.Mem1<SecondEnum.Mem1) //错误,不同枚举类型
Console.WriteLine("True");
}
}
上述代码声明了两个枚举类型,它们具有完全相同的结构和成员名:
- 第一个if语句是正确的,因为它比较统一枚举类型的不同成员;
- 第二个if语句产生一个错误,因为它试图比较来自不同枚举类型的成员,尽管它们的结构和成员名称完全相同;
.NET Enum类型(enum就是基于该类型的)还包括一些有用的静态方法:
- GetName方法以一个枚举类型对象和一个整数为参数,返回相应的枚举成员的名称;
- GetNames方法以一个枚举类型对象为参数,返回该枚举中所有成员的名称;
enum TrafficLight
{
Green,
Yellow,
Red
}
class Program
{
static void Main()
{
Console.WriteLine("Second member of TrafficLight is {0}\n",
Enum.GetName(typeof(TrafficLight), 1));
foreach (var name in Enum.GetNames(typeof(TrafficLight)))
Console.WriteLine(name);
}
}
执行结果: