12.1 枚举类型
枚举类型基实就是一些常量和一个实例字段。
强类型,枚举之间进行不能隐式转换
枚举类型直接派生于System.Enum,后者派生于System.ValueType,值类型,可以使用装箱/拆箱
不能定义方法/属性/事件。
同一个枚举中,多个枚举符号有相同的数值,数值转符号时,会返回其中第一个符号。(注意理解)
枚举类型要与使用它的类在同一级。
默认为int,可以指定枚举成员的类型,只有int,uint,byte,sbyte,long,ulong,short,ushort这8种基元类型。
可以使用操作符作用于枚举类型,实际上是作用于相应的value实例字段,如++。
可以将枚举类型的实例显示转型为另一个不同的枚举类型,其实是先转为值,再反过去在另一个枚举中根据值找对应枚举值。
可以显示将枚举类型的实例转型为一个数值类型。
public enum Color
{
Red,
Black,
White
}
public enum Sentence
{
hello,
bye
}
class Program
{
static void Main(string[] args)
{
//使用运算符,++
Color a = Color.Red;
++a;
Sentence s;
//两个不同的枚举相互转换, 但是发现Color.White值为2,相应在Sentence中找不到,所以直接返回整数2
s = (Sentence)Color.White; //等价于(Sentence)1;
//两个不同的枚举相互转换,返回枚举值Black
s = (Sentence)Color.Black;
//直接输出White枚举值, 显式转为整数
Console.WriteLine(Color.White); //输出white
Console.WriteLine((Int32)Color.White); //输出2
}
}
如 enum Color{Red=1, White=2}对应IL:
可以看到,枚举只是一个在其中定义一系列常量字段和实例字段的结构。
如果只使用Color.Red这样的类型,则会作为常量处理,从而代码不再引用定义了的枚举类型,可以删除相应的dll(具体见第7章);
如果使用Color c = new Color();这样的语法,那么依赖于引用,不可以删除相应的dll
枚举方法列表:
1.GetUnderlyingType静态方法,获取枚举类型值的核心类型:
//返回System.Int
Enum.GetUnderlyingType(typeof(Color));
2.ToString()方法
Color c = Color.Black;
Console.WriteLine(c); //输出Black
Console.WriteLine(c.ToString()); //输出Black
Console.WriteLine(c.ToString("G")); //输出Black,以上都是泛型格式
Console.WriteLine(c.ToString("D")); //输出1,十进制
Console.WriteLine(c.ToString("X")); //输出00000001,十六进制
对于c.ToString("X")十六进制格式,A-F始终是大写字母。此外,输出的数位个数取决于Color的基本类型:
byte/sbyte 2位
short/ushort 4位
int/uint 8位
long/ulong 16位
3.Format静态方法,功能基本同ToString()方法,但允许value传递一个数值,而不仅仅是一个Enum类型
Enum.Format(typeof(Color), 3, "G");
4.GetValues静态方法,返回一个数组,数组中为所有的枚举类型名称:
Color[] colors = (Color[])Enum.GetValues(typeof(Color));
5.static String GetName(Type enumType, Objbect value)静态方法,返回数值的字符串表示
6.static String[] GetNames(Type enumType),返回方法5的数组表示
7.Parse静态方法,将一个符号转换为枚举类型的一个实例:
Color cc1 = (Color)Enum.Parse(typeof(Color), "2");
Color cc2 = (Color)Enum.Parse(typeof(Color), "Black"); //可以是值,也可以是枚举类型名称
Color cc3 = (Color)Enum.Parse(typeof(Color), "Black", true); //可选参数true表示忽略大小写
8.IsDefined静态方法,判断一个数值对于一个枚举类型是否合法:
Enum.IsDefined(typeof(Color), 10); //返回false,Color中没有10
Enum.IsDefined(typeof(Color), "Black"); //返回true
常用于参数验证:
public void SetColor(Color c)
{
if (!Enum.IsDefined(typeof(Color), c))
{
//抛出异常,c不适合Color
}
}
注:Enum.IsDefined方法慎用,因为要区分大小写查找;使用反射机制,速度慢。
9.ToObject静态方法,一系列方法,如Enum.ToObject (Type, Byte),将Int32,Byte等类型实例转为枚举类型的实例(由于返回都是Object,所以要显示转型)
12.2 位标志 bit flag
位标志是一个位集合,其中有些位on,有些位off;而枚举是一个数值,是位集合中若干值的累加。
在枚举上应用[Flags]属性,就可以用枚举来表示一组可以组合的位标志,一般要定义一个值为0的None符号。
[Flags]
public enum FileAttributes
{
ReadOnly = 0x0001,
Hidden = 0x0002,
System = 0x0004
}
这时,就可以使用位运算来操作:
逻辑与,判断是否有这个枚举值
//以下代码,先取出文件的属性列表——是一个位集合,
//与FileAttributes.Hidden做逻辑:如果位集合中有FileAttributes.Hidden,则返回FileAttributes.Hidden
String file = @"C:Boot.ini";
FileAttributes attributes = File.GetAttributes(file);
if ((attributes & FileAttributes.Hidden) == FileAttributes.Hidden) { }
逻辑或,组合出所需的枚举列表,一般用来设置:
File.SetAttributes(path, FileAttributes.ReadOnly | FileAttributes.Hidden);
对位标记Enum调用ToString()方法,数值转字符串:
先检查是否应用了[Flags]属性
如果没有,就查找与该数值匹配的Enum符号并返回,没有匹配就返回数值。
如果有,就获取Enum降序排列的数值集合A,将A中的数值“按位与”Enum实例中的数值,将结果等于数值的Enum项附加到输出字符串,以逗号分割,如"Read,Write"
有两种特殊情况:
1.一个数值不能由Enum的若干项组合,则返回该数值
2.应该定义0。
对位标记Enum调用Parse()方法,字符串转数值:
Enum.Parse(typeof(Color), "Query");
Enum.Parse(typeof(Color), "Query, Read");
Enum.Parse(typeof(Color), "28");
处理过程如下:
1.删除字符串中所有空白字符
2.如果字符串以+/-/数字开始,那么字符串被认为是一个数字,直接转型为对应数值的字符串
3.以逗号分割字符串,在Enum中查找相应符号的数值,按位或取值并返回
不要对位标记Enum调用IsDefined()方法,因为,
传字符串,位标记中含有逗号,但永远找不到含有逗号的枚举符号;
传数值,永远返回false
第十三章 枚举类型与位标记
一、 枚举类型
1、 使用枚举类型的理由:
l 枚举类型是得程序更容易编写、阅读、维护,在代码中使用符号名称代替数字是程序设计的一贯主张。
2 强类型的,便于类型检验
2、 注意事项:
1枚举类型继承自System.Enum,System.Enum又继承自System.ValurType
2枚举类型不能定义方法、属性、事件
3枚举类型为常数而非只读字段,因此可能引入版本问题(见第八章的相关讨论)
4 将枚举类型与引用它的类型定义在同一层次上,可减少代码录入的工作量
3、 System.Enum中方法的应用:
l public static Type GetUnderlyingType(Type enumType);
获取用于保存枚举类型实例值得基础类型。声明某枚举类型使用的基础类型语法如下:
enum Human : byte
{
Male,
Female
}
则调用上述方法Enum.GetUnderlyingType(typeof(Human));将返回System.Byte;
2 public override string ToString();
public string ToString(string); //参数为格式字符串
public static string Format(Type enumType,object value,string format);
//Value – 要转换的值,format – 格式字符串(G,g,X,x,D,d,F,f)
3 public static Array GetValues(Type enumType);
获取枚举中常数值的数组
l public static string GetName(Type enumType,object value);
在指定枚举中检索具有指定值的常数的名称
4 public static string[] GetNames(Type enumType);
检索指定枚举中常数名称的数组。
l public static object Parse(Type, string);
public static object Parse(Type, string, bool);
将一个或多个枚举常数的名称或数字值的字符串表示转换成等效的枚举对象
l public static bool IsDefined(Type enumType,object value);
返回指定枚举中是否存在具有指定值的常数的指示,value为常数的值或名称
l 系列ToObject方法
返回设置为指定值的、指定枚举类型的实例
using System;
public enum Color
{
Red,
Black,
White
}
public enum Sentence
{
hello,
bye
}
class Program
{
static void Main(string[] args)
{
//GetUnderlyingType返回枚举类型值的核心类型
Console.WriteLine(Enum.GetUnderlyingType(typeof(Color)));
//ToString()方法
Color c = Color.Black;
Console.WriteLine(c); //输出Black
Console.WriteLine(c.ToString()); //输出Black
Console.WriteLine(c.ToString("G")); //输出Black,以上都是泛型格式
Console.WriteLine(c.ToString("D")); //输出1,十进制
Console.WriteLine(c.ToString("X")); //输出00000001,十六进制
Console.WriteLine( Enum.Format(typeof(Color), 1, "d"));
//Enum.GetValues获取枚举类型的符号
Color[] colors = (Color[])Enum.GetValues(typeof(Color));
foreach (Color color1 in colors )
{
Console.WriteLine(color1);
}
//Enum.GetName返回指定数值的所在枚举的符号,
Console.WriteLine(Enum.GetName(typeof(Color),1));
Console.WriteLine(Enum.Parse(typeof(Color), "Black"));
Console.WriteLine(Enum.IsDefined(typeof(Color), 10));
}
}
二、 位标记
l 使用System.FlagsAttributes定制特性,使得ToString或Format方法可以查找枚举数值中的每个匹配符号,将它们连接为一个字符串,并用逗号分开;Parse方法可用该特性拆分字符串并得到复合的枚举类型
l 使用格式字符串F或f 也有同样的效果
下面的示例说明上述情况
using System;
[Flags] //定制特性
public enum Human : byte //定制基本类型
{
Male = 0x01,
Female = 0x10
}
public class EnumTest
{
public static void Main()
{
Human human = Human.Male | Human.Female; //人妖?
Console.WriteLine(human.ToString()); //使用Flags定制特性的情况
//Console.WriteLine(human.ToString("F")); //没有使用Flags定制特性的情况
Console.WriteLine(Enum.Format(typeof(Human), human, "G"));//使用Flags定制特性的情况
//Console.WriteLine(Enum.Format(typeof(Human), human, "F"));//没有使用Flags定制特性的情况
human = (Human)Enum.Parse(typeof(Human), "17");
Console.WriteLine(human.ToString()); //使用Flags定制特性的情况
//Console.WriteLine(human.ToString("F")); //没有使用Flags定制特性的情况
}
}
/*运行结果
Male, Female
Male, Female
Male, Female
*/
注:上述程序中的注释为不使用Flags特
enum Complexion//肤色
{
White,
Black,
Yellow
}
这是一个枚举,没有什么特别。
说一下枚举的特点吧,其实枚举就是一系列符号和一系列对应的值,在上面的例子中,枚举的基类型是Int32类型,这是默认的类型,对应的各个枚举项,White=0,Black=1;Yellow=2;这就是一个符号与值的对应,CLR在用枚举时,是用的值,并不是符号,符号是开发人员用来能明确其值的含义的。
所有枚举都是从System.Enum派生来的,System.Enum又是从System.ValueType派生来的,System.ValueType的父类是System.Object。即然从System.Enum派生的,当然枚举都属于值类型了。
现在我们看看IL中的Complexion枚举是个什么样子
il语言:
.class private auto ansi sealed Complexion extends System.Enum
{
.field public static literal valuetype complexion black=int32(0x000000001)
.field public static literal valuetype Complexion while=int32(0x000000002)
.field public static literal valuetype Complexion yellow=int32(0x000000003)
.field public specialname rtspecialname int32 value_
}
在IL中我们能看到它是从System.Enum继承的,还会发现所有的符号都是一个常量,即static literal这个标识。
其实可以用这样一个C#代码来说明枚举Complexion
Internal struct Complexion:System.Enum
{
public const Complexion White==(Complexion)0;
public const Complexion Black==(Complexion)1;
public const Complexion Yellow==(Complexion)2;
public Int32 value_;
}
当然,上面的代码如果写到C#环境中是编译不过的,因为System.Enum是个特殊的类,不允许派生子类型。
在上面的伪代码中,可以看到,每个符号(White,Black,Yellow),都是一个常量,关且是Complexion类型的。同时在Complexion内部还有一个常量,Value_,从有关资料查询说是包含一个Complexion的符号的值。同时期待了解这个变量的准确用途的朋友告知。 Enumerted Type
1.枚举类型实际上一对名称和值的集合。
2.使用枚举的好处:
1)易于识别
2)枚举类型是强类型的
3.枚举类型从IL语言来看,实际上是编译成几个常量字段和一个实例字段。
4.枚举类型的定义和类一个级别,但是不能定义属性,方法,事件等。
5.枚举类型继承自System.Enum,后者继承自System.valueType,后者继承自System.Object.故属于值类型。
6.枚举类型的方法:
1)Enum.GetUnderlyingType()获得枚举类型的基类型(C#中默认是int)
2)Enum.GetValues() 返回一个枚举类型名称数组
3)Enum.GetName() 返回名称标示
4)Enum.Format()
5)Enum.Parse()
6)Enum.IsDefined()
7.可以指定int,uint,byte,sbyte,long,ulong,short,ushort这8种基元类型为枚举类型的基类型,默认为int.
8.枚举类型经常用在方法参数,返回值,属性,字段中。
C# 枚举(enum)-基础
枚举提供成组的常数值,它们有助于使成员成为强类型以及提高代码的可读性。在 C# 中,使用 enum 来声明枚举。
枚举分为简单枚举和标志枚举两种,将在第三节中详细介绍这二者。
基本语法示例
enum Day { Sun, Mon, Tue, Wed, Thu, Fri, Sat };
以下格式也可以:
enum Day
{
Sun,
Mon,
Tue,
Wed,
Thu,
Fri,
Sat
};
枚举类型
枚举类型可以是:byte、sbyte、short、ushort、int、uint、long、ulong,如果没有指定类型,则默认为 int 类型。指定类型示例:
enum Day : byte { Sun, Mon, Tue, Wed, Thu, Fri, Sat };
枚举数的值
默认情况下,第一个枚举数的值为 0,后面每个枚举数的值在前一个枚举数的值基础上递增 1。当然,也可以自行指定,例如:
enum Day { Sun = 1, Mon, Tue, Wed, Thu, Fri, Sat };
enum Range { MIN = 0, MAX = 255 };
enum Range2 { MIN, M1 = 50, M2, MAX = 255 }; //MIN 为 0,M2 为 51
枚举数大小写敏感
比如以下枚举有两个枚举数:
enum EnumTest { Sun, sun }; //尽管如此,我们仍不推荐这样写
取枚举数的值
虽然枚举有类型,但取枚举数的值时,仍然需要进行类型转换。
public partial class _ENUM : System.Web.UI.Page
{
enum Range { MIN = 0, MAX = 255 };
protected void Page_Load(object sender, EventArgs e)
{
Response.Write(Range.MAX); //输出为:MAX
Response.Write("<br>");
Response.Write(((int)Range.MAX).ToString()); //输出为:255
}
}
枚举不能放在函数中
枚举可以与类平级,也可以作为类的 field,但不能放在函数中。可以加 public 等修饰符。
以下是使用枚举时几条好的建议。
优先考虑使用枚举,而不是类的静态常量
比如:
public static class Day
{
public static int Sun = 1;
public static int Mon = 2;
public static int Tue = 3;
//...
}
应该使用如下的枚举:
enum Day { Sun = 1, Mon, Tue, Wed, Thu, Fri, Sat };
如果参数、返回值、变量等类型可以是枚举,则不要使用其它基础类型
比如:
Range r = Range.MAX; //好
int r = (int)Range.MAX; //不好
枚举命名
枚举一般使用名词或名词组合,简单枚举使用单数,标志枚举使用复数。
大多数情况下不需要更改枚举的默认类型
也就是说大多数情况下,使用 int(System.Int32)作为枚举类型。除非:
- 枚举是标志枚举,且标志多于 32 个(此时 int 类型装不下)。
- 枚举被非常大量且频繁地使用,为了节约空间使用小于 int 的类型。
- 不得不使用其它类型的情况。
不要在枚举中设置哨兵
我们可能觉得在枚举的两端加上哨兵,这样在判断一个数是否在枚举中时,只需要判断是否在哨兵之中。非常不幸,我们不应该这么做,这破坏了枚举的意义。
enum Day {FirstValue, Sun, Mon, Tue, Wed, Thu, Fri, Sat, LastValue }; //FirstValue、LastValue 应该去掉
- 简单枚举包含的值不用于组合,也不用于按位比较。
- 标志枚举应使用按位 OR 操作进行组合。
简单枚举
前面提到的 Day、Range 都可以称之为简单枚举,因为不能将他们各自的值组合起来。
标志枚举
标志枚举的设计有两点要注意。
- 指明 FlagsAttribute,以指示可以将枚举作为位域(即一组标志)处理。
- 枚举中各标志的值应该是以 2 的幂来赋值,即:1、2、4、8、16、32……
举个例子,假如我们在设计 Windows 窗口程序,窗口有最小化、最大化、关闭按钮,我们想任意组合显示,也就是说我们可以显示其中的任意 0 个或一个或多个按钮。
如果使用简单枚举,按照排列组合,我们要使用 1 + 3 + 3 + 1 = 8 个枚举数,如果这里不是三个按钮,而是四个按钮,枚举数就更多了。所以这样不现实。
为什么这里使用简单枚举不现实呢?因为简单枚举不能组合,采用标志枚举就可以轻松解决了。
[Flags]
public enum WindowStyle
{
MINIMUM_BUTTON = 1, //十六进制表示为 0x0001
MAXIMUM_BUTTON = 2,
CLOSE_BUTTON = 4
}
我们在设置窗口样式时,利用 OR 自由组合:
WindowStyle ws = WindowStyle.MINIMUM_BUTTON | WindowStyle.CLOSE_BUTTON; //表示既有 MINIMUM_BUTTON 也有 CLOSE_BUTTON
这就是为什么标志的值要按 2 的幂排列的原因了,也是为什么标志多于 32 个时不能使用 int 类型的原因了。
通常我们为常用的标志组合提供特殊的枚举值
仍然以上述窗口为例,可知大多数情况下,我们均要显示这三个按钮,所以每次使用时都要用:
WindowStyle ws = WindowStyle.MINIMUM_BUTTON | MAXIMUM_BUTTON | WindowStyle.CLOSE_BUTTON;
实在有些繁琐,我们可以修改枚举为如下:
[Flags]
public enum WindowStyle
{
MINIMUM_BUTTON = 1,
MAXIMUM_BUTTON = 2,
CLOSE_BUTTON = 4,
ALL_BUTTON = 7
}
增加一个 ALL_BUTTON 为前三个标志的值。使用时直接用 ALL_BUTTON 就可以了。
从长远来看,创建枚举可以节省大量的时间,减少许多麻烦。使用枚举比使用无格式的整数至少有如下三个优势:
● 枚举可以使代码更易于维护,有助于确保给变量指定合法的、期望的值。
● 枚举使代码更清晰,允许用描述性的名称表示整数值,而不是用含义模糊的数来表示。
● 枚举使代码更易于键入。在给枚举类型的实例赋值时,VS.NET IDE会通过IntelliSense弹出一个包含可接受值的列表框,减少了按键次数,并能够让我们回忆起可能的值。public enum FileStates{Begin=1,Pause=2,RollBack=3,Success=4};
枚举类型都是值类型。System.Enum是一个抽象类(abstract class),所有枚举类型都直接继承自它,当然也同时继承了它的所有成员。所有的值类型都是System.ValueType的后代,枚举类型也不例外,枚举类型直接继承自System.Enum,而System.Enum却又直接继承自System.ValueType的,所以,枚举类型也是System.ValueType的后代。
值类型都是System.ValueType的后代”,但System.ValueType的后代不全是值类型,System.Enum就是唯一的特例!在System.ValueType的所有后代中,除了System.Enum之外其它都是值类型。事实上,我们可以在.NET的源代码中找到System.Enum的声明:
public abstract class Enum : ValueType, IComparable, IFormattable, IConvertible
- 1. 所有枚举类型(enum type)都是值类型。
- 2. System.Enum和System.ValueType本身是引用类型。
- 3. 枚举类型(enum type)都是隐式的直接继承自System.Enum,并且这种继承关系只能由编译器自动展开。但System.Enum本身不是枚举类型(enum type)。
- 4. System.Enum是一个特例,它直接继承自System.ValueType,但本身却是一个引用类型。
A:枚举类型可以被装箱成System.Enum、System.ValueType、System.Object或者System.IConvertible、System.IFormattable、System.IComparable。
注意:在.NET 1.1上,枚举类型只能被装箱到System.Enum、System.ValueType、System.Object;而在.NET 2.0上,枚举类型还能被装箱到System.Enum所实现的三个接口:System.IConvertible、System.IComparable、System.IFormattable。对应的装箱操作既可以为隐式的也可以是显式的。
枚举类型与整数类型有一定的关系。事实上,每一个枚举类型都有与之相对应的整数类型,我们称该整数类型为底层类型(underlying type),默认的情况下使用,.NET使用System.Int32。当然,你可以手动将其指定为其他的整数类型:
能被指定为枚举的底层类型的只能是如下所列的整数类型:byte, sbyte, short, ushort, int, uint, long, ulong。
如果你没有手动指定成员的值的话,从上往下看,各成员的值为:0, 1, 2, ...。说罢了,就是一个非负整数等差数列,其初值为0,步长为1。例如:
public enum Alignment
{
Left, // 0
Center, // 1
Right // 2
}
那么被赋值的成员的值就是你所指定的值。当然,无论你是否手动指定枚举成员的值,递增步长都不会变,总是为1。为了测试你是否理解,请说出下面枚举个成员的值以及你的判断理由(请用人脑而不是电脑来运行以下代码):
public enum DriveType : sbyte
{
CDRom,
Fixed = -2,
Network,
NoRootDirectory = -1,
Ram,
Removable = Network * NoRootDirectory,
Unknown
}
public enum CustomerKind
{
Normal = 90,
Vip = 80,
SuperVip = 70,
InActive = 100
}
public class Customer
{
public readonly CustomerKind Kind;
private double m_Payment;
public double Payment
{
return m_Payment * (int)Kind / 100;
}
为枚举CustomerKind的每个成员都赋了一个特定的值,该值其实就是顾客购物折扣百分率。而在Customer类中,Payment属性就通过强类型转换来获取枚举成员的值(也就是购物折扣率),并用于货款计算。从这里可以看出,获取枚举成员的值还可以通过强类型转换方式。
// Code here
}
枚举类型可以强制转换为整数,整数也可以强制转换为枚举类型
Alignment a = (Alignment)1;但这种机制可能使你遇到一些麻烦
public static bool IsAlignment(Alignment a)
{
switch(a)
{
case Alignment.Left:
case Alignment.Center:
case Alignment.Right:
return true;
default:
return false;
}
}
枚举类型转换(解析)成字符串类型
最简单的方法就是使用System.Enum的public override string ToString(); 或者把枚举类型转换为IConvertible接口,再调用该接口的string ToString(IFormatProvider provider);
static void Main()
{
Alignment a = Alignment.Right;
Console.WriteLine("Alignment is {0}.", a.ToString());
FontStyle fs = FontStyle.Bold | FontStyle.Underline;
Console.WriteLine("FontStyle is {0}.", fs.ToString());
}
手动指定格式参数:Console.WriteLine("Alignment is {0}.", a.ToString("d"));
一个表示枚举成员的字符串,如何将其解析为对应枚举类型:
这时你就需要System.Enum的public static object Parse( Type enumType, string value, bool ignoreCase );
static void Main()
{
string name = "Right";
Alignment a = (Alignment)Enum.Parse(typeof(Alignment), name, false);
Console.WriteLine(a.ToString());
string names = "Bold, Italic, Underline";
FontStyle fs = (FontStyle)Enum.Parse(typeof(FontStyle), names, false);
Console.WriteLine(fs.ToString());
}
不应该使用枚举的情况:
枚举类型表达了一种稳定的分类标准。当你查看.NET Framework BCL中的枚举类型,你会发现它们几乎没有任何改变的可能或者趋势,表现出一种稳定性。所以,当你所要表达的分类标准也同样具备这种稳定性时,你就可以考虑枚举类型了。那么什么情况下不使用枚举呢?一般说来,当分类标准不闭合时——即新的子分类随时有可能产生或者现有子分类随时有可能被替换——你就应该考虑使用其他的方式来表达了.