C# 语言规范
14.3 枚举成员
枚举类型声明的体用于定义零个或多个枚举成员,这些成员是该枚举类型的命名常数。任意两个枚举成员不能具有相同的名称。
enum-member-declarations:(枚举成员声明:)
enum-member-declaration(枚举成员声明)
enum-member-declarations , enum-member-declaration(枚举成员声明 , 枚举成员声明)
enum-member-declaration:(枚举成员声明:)
attributesopt identifier(属性可选 标识符)
attributesopt identifier = constant-expression(属性可选 标识符 = 常数表达式)
每个枚举成员均具有相关联的常数值。此值的类型就是包含了它的那个枚举的基础类型。每个枚举成员的常数值必须在该枚举的基础类型的范围之内。示例
enum Color: uint
{
Red = -1,
Green = -2,
Blue = -3
}
产生编译时错误,原因是常数值 -1、-2 和 –3 不在基础整型 uint 的范围内。
多个枚举成员可以共享同一个关联值。示例
enum Color
{
Red,
Green,
Blue,
Max = Blue
}
显示一个枚举,其中的两个枚举成员(Blue 和 Max)具有相同的关联值。
一个枚举成员的关联值或隐式地、或显式地被赋值。如果枚举成员的声明中具有“常数表达式”初始值设定项,则该常数表达式的值(它隐式转换为枚举的基础类型)就是该枚举成员的关联值。如果枚举成员的声明不具有初始值设定项,则它的关联值按下面规则隐式地设置:
如果枚举成员是在枚举类型中声明的第一个枚举成员,则它的关联值为零。
否则,枚举成员的关联值是通过将前一个枚举成员(按照文本顺序)的关联值加 1 得到的。这样增加后的值必须在该基础类型可表示的值的范围内;否则,会出现编译时错误。
示例
using System;
enum Color
{
Red,
Green = 10,
Blue
}
class Test
{
static void Main() {
Console.WriteLine(StringFromColor(Color.Red));
Console.WriteLine(StringFromColor(Color.Green));
Console.WriteLine(StringFromColor(Color.Blue));
}
static string StringFromColor(Color c) {
switch (c) {
case Color.Red:
return String.Format("Red = {0}", (int) c);
case Color.Green:
return String.Format("Green = {0}", (int) c);
case Color.Blue:
return String.Format("Blue = {0}", (int) c);
default:
return "Invalid color";
}
}
}
输出枚举成员名称和它们的关联值。输出为:
Red = 0
Green = 10
Blue = 11
原因如下:
枚举成员 Red 被自动赋予零值(因为它不具有初始值设定项并且是第一个枚举成员)。
枚举成员 Green 被显式赋予值 10。
枚举成员 Blue 被自动赋予比文本上位于它前面的成员大 1 的值。
枚举成员的关联值不能直接或间接地使用它自己的关联枚举成员的值。除了这个循环性限制外,枚举成员初始值设定项可以自由地引用其他的枚举成员初始值设定项,而不必考虑它们所在的文本位置的排列顺序。在枚举成员初始值设定项内,其他枚举成员的值始终被视为属于所对应的基础类型,因此在引用其他枚举成员时,没有必要使用强制转换。
示例
enum Circular
{
A = B,
B
}
产生编译时错误,因为 A 和 B 的声明是循环的。A 显式依赖于 B,而 B 隐式依赖于 A。
枚举成员的命名方式和作用范围与类中的字段完全类似。枚举成员的范围是包含了它的枚举类型的体。在该范围内,枚举成员可以用它们的简单名称引用。在所有其他代码中,枚举成员的名称必须用它的枚举类型的名称限定。枚举成员不具有任何声明可访问性,如果一个枚举类型是可访问的,则它所含的所有枚举成员都是可访问的。
© Microsoft Corporation。保留所有权利。
先来看这段NUnit测试代码,我们希望用反射机制在运行时访问一个对象的枚举类型的域或属性:
[TestFixture]
public class PaymentInfo
{
public enum PaymentType
{
Cash, CreditCard, Check
}
public PaymentType Type;
public void Test()
{
PaymentInfo payment = new PaymentInfo();
payment.Type = PaymentType.Cash;
System.Reflection.FieldInfo enumField = GetType().GetField("Type");
int paymentTypeInt32;
paymentTypeInt32 = (int)enumField.GetValue(payment);
Assert.AreEqual((int)PaymentType.Cash, paymentTypeInt32);
enumField.SetValue(payment, paymentTypeInt32);
Assert.AreEqual(PaymentType.Cash, payment.Type);
}
}
实际上运行测试时发现在标红的这行上抛出一个异常:“对象类型无法转换为目标类型”。究其原因,原来是因为CLR的反射机制不允许枚举类型与整数类型之间隐式转换。不过C#编译器还是允许我们通过强制类型转换的语法来进行两者间的显式转换。
在这个测试中,使之通过的办法其实非常简单:把划线部分强制转换为枚举类型即可,如:(PaymentType)paymentTypeInt32。可问题是:在运行时如何动态转换类型呢?比如说我在写ElegantDAL的时候,需要将从数据库读出的一个类型为int的数值写入到要返回的对象的一个枚举型字段中,此时我只有fieldInfo、columnValue和resultObject,然而写成fieldInfo.SetValue(resultObject, columnValue)就会出现前面提到的错误,可是我又只有一个运行时的Type信息(fieldInfo.FieldType),我又不能写成fieldInfo.SetValue(resultObject, (fieldInfo.FieldType)columnValue)……
只好将这种情况列为一个特例处理,而我们的救兵则是Enum.ToObject()方法——你知道有更好的方法解决这个问题吗?
枚举类型是C#中又一种轻量级的值类型,C#用枚举来表达一组特定的值的集合行为,比如Windows窗体可选的状态,按钮控件的风格等。下面的程序伪码展示了典型的枚举用法:
public enum WritingStyle
{
Classical,
Modern,
Elegant,
}
class Essay
{
public void Write(WritingStyle writingStyle)
{
switch (writingStyle)
{
case WritingStyle.Classical:
// 古典的写作风格
break;
case WritingStyle.Modern:
// 现代的写作风格
break;
case WritingStyle.Elegant:
// 典雅的写作风格
break;
default:
throw(new System.ArgumentException("Invalid Writing Style"));
}
}
}
注意上面的枚举符号Classical, Modern, Elegant之间用逗号“,”而不是分号“;”来分隔。其中最后一个枚举值Elegant之后可以省去逗号分隔符。
和结构一样,C#中的枚举不允许也有自己的继承父类System.Enum,同样的,枚举不能被继承,也没有abstract一说。System.Enum类为枚举类型提供了很多好用的功能操作。比如我们可以通过GetName方法得到我们声明枚举值的字符串符号表示。下面的例子显示了一些比较常用的操作:
using System;
public enum WritingStyle
{
Classical,
Modern,
Elegant,
}
class Test
{
public static void Main()
{
WritingStyle dw=WritingStyle.Modern;
Console.WriteLine(Enum.GetName(typeof(WritingStyle),dw));
Console.WriteLine(dw.ToString());
Console.WriteLine(Enum.GetUnderlyingType(typeof(WritingStyle)));
}
}
其中的最后一行输出了System.Int32,这是怎么回事?我们知道int是System.Int32的简写形式,难道我们的WritingStyle枚举类型和整数int类型有什么关系吗?
是的,C#的枚举和整数值之间严格区分,比如我们就不能在上面的代码中作dw=1类似的赋值。但每个枚举值却的的确确都有一个整数类型的数值相对应,而且可以转换,只不过这种转换必须用明晰的转型语法来表达。我们知道在C#中整数类型有byte, sbyte, short, ushort, int, uint, long ,ulong共八种,那么C#的枚举值对应的是哪一种整数类型呢?它关系到我们的枚举类型能够容纳的枚举值的数量。实际上C#枚举类型支持这八种整数类型的任何一种,根据我们的需要,可以在声明枚举类型的时候来指定,如“public enum WritingStyle :byte”就指定了它的枚举值的整数类型为byte。该类型可以通过上面所述的Enum.GetUnderlyingType方法来获得。如果不明确指定,C#将默认采用int类型作为枚举数值的类型。既然枚举值实际上也是一种整数值,那么它的大小呢?读者将下面的代码加在上面的Main函数内,就可看到结果了。
foreach(object obj in Enum.GetValues(typeof(WritingStyle)))
{
Console.WriteLine(obj.ToString() +" : "+(int)obj);
}
上面的代码将产生以下输出:
Classical : 0
Modern : 1
Elegant : 2
可以看到,C#默认地从0起为我们的枚举值赋了一个隐含的值。当然,我们也可以自己为枚举值指定数值:
public enum WritingStyle
{
Classical=0,
Modern=10,
Elegant=100,
}
我们甚至可以象下面这样,前提是我们必须保证编译时能够计算出他们各自的数值。
public enum WritingStyle
{
Classical=0,
Modern=10+Classical,
Elegant=10+Modern,
}
枚举值也拥有象整数那样的比较,逻辑,算术等操作行为,看下面的例子:
using System;
public enum WritingStyle
{
Classical=0,
Modern=10+Classical,
Elegant=10+Modern,
}
class Test
{
public static void Main()
{
WritingStyle dw=WritingStyle.Modern;
Console.WriteLine(dw+10);
Console.WriteLine(dw+9);
}
}
当计算出来的数值等于某个枚举值的实际大小,那么显示的是该枚举值的符号名称,否则显示的将是整数值。