C# 枚举(位标志)
C# 枚举(位标志)
程序员们长期使用单个字(singleword)的不同位作为表示一组开/关标志的紧凑方法。本节
将其称为标志字(flagword)。枚举提供了实现它的简便方法。
一般的步骤如下。
(1)确定需要多少个位标志,并选择一种有足够多位的无符号类型来保存它。
(2)确定每个位位置代表什么,并给它们一个名称。声明一个选中的整数类型的枚举,每个成员由一个位位置表示。
(3)使用按位或(0R)运算符在持有该位标志的字中设置适当的位。
(4)使用按位与(AND)运算符或HasFlag方法检查是否设置了特定位标志。
例如,下面的代码展示了枚举声明,表示纸牌游戏中一副牌的选项。底层类型uint足够满
足4个位标志的需要了。注意代码的下列内容。
- 成员有表示二进制选项的名称。
- 每个选项由字中一个特定的位位置表示。位位置持有一个0或一个1。
- 因为一个位标志表示一个或开或关的位,所以你不会想用0作为成员值。它已经有了一
个含义:所有的位标志都是关。
- 在十六进制表示法中,每个十六进制数字用4位来表示。由于位模式和十六进制表示法
之间的这种直接联系,所以在处理位模式时,经常使用十六进制而不是十进制表示法。 - 从C#7.0开始,可以使用二进制表示法了。
- 使用Flags特性装饰(decorate)枚举实际上不是必要的,但可以带来一些额外的便利,
我们很快会讨论这一点。特性表现为用中括号括起来的字符串,出现在语言构造之前。
在本例中,特性出现在枚举声明之前。特性将在第25章阐述。
[Flags]
enum CardDeckSettings:uint
{SingleDeck=0x01,//位0LargePictures=0x02,//位1FancyNumbers=0x04,//位2Animation=0x08 //位3
}
图12-4阐明了这个枚举。
[外链图片转存中…(img-OrLrOVfG-1749975593004)]
要创建一个带有适当的位标志的字,需要声明一个该枚举类型的变量,并使用按位或运算符
设置需要的位。例如,下面的代码设置了4个选项中的3个:
要判断标志字是否包含特定的位标志集,可以使用枚举类型的HasFlag布尔方法。在标志字上调用HasFlag方法,并将要检查的位标志作为参数。如果设置了指定的位标志,HasF1ag返回
true,否则返回false。
HasFlag方法还可以检测多个位标志。例如,如下的代码检查叩标志字是否设置了Animation
和FancyNumbers位。代码做了如下事情。
- 第一行语句创建了一个测试字实例,叫作testFlags,设置了Animation和FancyNumbers
标志位。 - 然后把testFlag作为参数传给HasFlag方法。
HasFlag检测是否测试字中的所有标志都在ops标志字中进行了设置。如果是的话,HasFlag
返回true,否则返回false。
另一种判断是否设置了一个或多个指定位的方法是使用按位与运算符。例如,与上面类似,
下面的代码检查一个标志字是否设置了FancyNumbers位标志。它把该标志字和位标志相与,然后
与位标志比较。如果在原始标志字中设置了这个位,那么与操作的结果将和位标志具有相同的位
模式。
图12-5阐明了创建一个标志字然后检查是否设置了某个特定位的过程。
Flags特性
前面的代码在枚举声明之前使用了Flags特性:
[Flags]
enum CardDeckSettings:uint
{....
}
Flags特性不会改变计算结果,却提供了一些方便的特性。首先,它通知编译器、对象浏览
器以及其他查看这段代码的工具,该枚举的成员不仅可以用作单独的值,还可以组合成位标志。
这样浏览器就可以更恰当地解释该枚举类型的变量。
其次,它允许枚举的ToString方法为位标志的值提供更多的格式化信息。Tostring方法以
一个枚举值为参数,将其与枚举的常量成员相比较。如果与某个成员相匹配,ToString返回该成
员的字符串名称。
例如,看看下面的代码,该枚举开头没有Flags特性:
enum CardDeckSettings:uint
{SingleDeck=0x01, //位0LargePictures=0x02,//位1FancyNumbers=0x04,//位2Animation=0x08 //位3
}class Program{static void Main(){CardDeckSettings ops;ops=CardDeckSettings.FancyNumbers;Console.WriteLine(ops.ToString());ops=CardDeckSettings.FancyNumbers|CardDeckSettings.Animation;Consoele.WriteLine(ops.ToString());}
}
在这段代码中,Main做了以下事情:
- 创建枚举类型cardDeckSettings的变量,设置一个位标志,并打印变量的值(即FancyNumbers);
- 为变量赋一个包含两个位标志的新值,并打印它的值(即12)。
作为第二次赋值的结果而显示的值12是ops的直。它是一个int,因为FancyNumbers将位
设置为值4,Animation将位设置为值8,因此最终得到int直12。在赋值语句之后的WriteLine方法中,ToString方法会查找哪个枚举成员具有值12,由于没有找到,因此会打印出12。
然而,如果在枚举声明前加上Flags特性,将告诉ToString方法位可以分开考虑。在查找值
时,Tostring会发现12对应两个分开的位标志成员一一FancyNumbe巧和Animation,这时将返回
它们的名称,用逗号和空格隔开。
运行包含Flags特性的代码,结果如下:
使用位标志的示例
[Flags]
enum CardDeckSettings:uint
{SingleDeck=0x01, //位0LargePictures=0x02,//位1FancyNumbers=0x04, //位2Animation=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 -{UseSingleDeck}" );Console.WriteLine($"Use Large Pictures -{UseBigPics}");Console.WriteLine($"Use Fancy Numbers -{UseFancyNumbers}");Console.WriteLine($"Show Animation -{UseAnimation}");Console.WriteLine($" Show Animation and FancyNumbers -{0}",UseAnimationAndFancyNumbers);}
}class Program
{satic void Main(){MyClass mc=new MyClass();CardDeckSettings ops=CardDeckSettings.SingleDeck | CardDeckSettings.FancyNumbers| CardDeckSettings.Animation;mc.SetOptions(ops);mc.PrintOptions();}
}
运行结果
ption Settings:Use Single Deck -TrueUse Large Pictures -falseUse Fancy Numbers -TrueShow Animation -TrueShow Animation and FancyNumbers -True