本文继续介绍C#枚举的常见问题与答案。
Q:我留意到Code #02中的
.field public static literal Aligment Center = int32(0x00000001)
该语句明显是整数赋值,这是否说明C#枚举类型实质上是整数类型?
A:这说明枚举类型与整数类型的确有一定的关系。事实上,每一个枚举类型都有与之相对应的整数类型,我们称该整数类型为底层类型(underlying type),默认的情况下使用,.NET使用System.Int32。当然,你可以手动将其指定为其他的整数类型:
//
Code #09
public
enum
Alignment :
byte
{
Left,
Center,
Right
}
注意,能被指定为枚举的底层类型的只能是如下所列的整数类型:byte, sbyte, short, ushort, int, uint, long, ulong。
Q:为何我们需要指定枚举类型的底层类型?
A:你完全可以让它接受默认的底层类型。请留意Code #08,你完全找不到“Center”这个字眼,然而在C#代码中,它却是存在的,为什么呢?这是因为代码在编译的时候,编译器把枚举类型转换为与之对应的底层类型的数值来处理。Code #08的L_0000实际上就是把类型为System.Int32的数值1推入堆栈,而不是把“Center”推入堆栈。事实上,底层类型说明了如何为枚举类型分配空间,不同的底层类型所占用的资源不同,大概当你在受限系统上进行开发的话,你就可能需要注意一下了。
C#枚举的赋值
Q:枚举成员的值是怎样规定的?
A:如果你没有手动指定成员的值的话,从上往下看,各成员的值为:0, 1, 2, ...。说罢了,就是一个非负整数等差数列,其初值为0,步长为1。例如:
//
Code #10
public
enum
Alignment
{
Left,
//
0
Center,
//
1
Right
//
2
}
Q:如果我有手动指定某些成员的值呢?
A:那么被赋值的成员的值就是你所指定的值。当然,无论你是否手动指定枚举成员的值,递增步长都不会变,总是为1。为了测试你是否理解,请说出下面枚举个成员的值以及你的判断理由(请用人脑而不是电脑来运行以下代码):
Code #11
//
Code #11
public
enum
DriveType :
sbyte
{
CDRom,
Fixed
=
-
2
,
Network,
NoRootDirectory
=
-
1
,
Ram,
Removable
=
Network
*
NoRootDirectory,
Unknown
}
Q:我们如何获取枚举成员的值,无论成员是否被手动赋值?
A:你可以使用System.Enum的
public
static
Array GetValues(Type enumType);
该方法返回一个包含所有枚举成员的数组:
Code #12
//
Code #12
//
See Code #01 for Alignment.
public
static
void
Main()
{
Alignment[] alignments
=
(Alignment[])Enum.GetValues(
typeof
(Alignment));
Console.WriteLine(
"
Wanna see the values of Alignment's menbers?
"
);
foreach
(Alignment a
in
alignments)
Console.WriteLine(
"
{0:G} = {0:D}
"
, a);
}
//
Output:
//
Wanna see the values of Alignment's menbers?
//
Left = 0
//
Center = 1
//
Right = 2
Q:如果我只需要其中某些枚举成员的值呢?
A:那么你可以把枚举转换为IConvertible接口,再调用对应的方法:
Code #13
//
Code #13
//
See Code #01 for Alignment.
public
static
void
Main()
{
IConvertible ic
=
(IConvertible)Alignment.Center;
int
i
=
ic.ToInt32(
null
);
Console.WriteLine(
"
The value of Alignment.Center is {0}.
"
, i);
}
//
Output:
//
The value of Alignment.Center is 1.
Q:为什么需要手动指定枚举成员的值?
A:一般情况下,使用默认的赋值规则就足够了,但某些情况下,为枚举成员指定一个与实际情况(模型)相符的值可能更有意义,这要视你具体所建的模型而定。
还是让我们来一个实际的例子:
Code #14
//
Code #14
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
;
}
//
Code here
}
我为枚举CustomerKind的每个成员都赋了一个特定的值,该值其实就是顾客购物折扣百分率。而在Customer类中,Payment属性就通过强类型转换来获取枚举成员的值(也就是购物折扣率),并用于货款计算。从这里可以看出,获取枚举成员的值还可以通过强类型转换方式。
Q:既然枚举类型可以强制转换为整数,那么整数是否也可以强制转换为枚举类型?
A:答案是肯定的。
//
Code #15
//
See Code #01 for Alignment.
Alignment a
=
(Alignment)
1
;
但这种机制可能使你遇到一些麻烦:
Code #16
//
Code #16
//
See Code #01 for Alignment.
class
Program
{
static
void
Main()
{
Foo((Alignment)
12345
);
}
static
void
Foo(Alignment a)
{
//
Code here
}
}
你无法避免有人进行这样的恶作剧!!
Q:那么是否有办法对付这些恶作剧的人?
A:Sure!我们总不能假设人人都那么守规矩,所以,我们需要System.Enum的
public
static
bool
IsDefined(Type enumType,
object
value);
现在我们把Code #15的Foo方法改进一下:
Code #17
//
Code #17
//
See Code #01 for Alignment.
static
void
Foo(Alignment a)
{
if
(
!
Enum.IsDefined(
typeof
(Alignment), a))
throw
new
ArgumentException(
"
DO NOT MAKE MISCHIEF!
"
);
//
Code here
}
这样,恶作剧的人将会收到一个警告(异常消息)。当然,我们不排除有人是由于一时大意才造成这样的“恶作剧”,那么IsDefined方法同样可以帮助你处理好这些情况。
Q:我认为我们还可以使用条件判断语句来处理这种情况:
Code #18
//
Code #18
//
See Code #01 for Alignment.
static
void
Foo(Alignment a)
{
if
(a
!=
Alignment.Left
&&
a
!=
Alignment.Center
&&
a
!=
Alignment.Right)
throw
new
ArgumentException(
"
DO NOT MAKE MISCHIEF!
"
);
//
Code here
}
或者
Code #19
//
Code #19
//
See Code #01 for Alignment.
static
void
Foo(Alignment a)
{
switch
(a)
{
case
Alignment.Left:
Console.WriteLine(
"
Cool~
"
);
break
;
case
Alignment.Center:
Console.WriteLine(
"
Well~
"
);
break
;
case
Alignment.Right:
Console.WriteLine(
"
Good~
"
);
break
;
default
:
Console.WriteLine(
"
DO NOT MAKE MISCHIEF!
"
);
break
;
}
}
A:你绝对可以这样做!事实上,如果你处于以下情况之一的话:
1. Alignment枚举代码不会被修改
2. 你不希望使用Alignment枚举新增的特性
那么我会推荐使用你的处理方式。而且,你还可以为自己的代码定义一个这样的方法:
Code #20
//
Code #20
//
See Code #01 for Alignment.
public
static
bool
IsAlignment(Alignment a)
{
switch
(a)
{
case
Alignment.Left:
return
true
;
case
Alignment.Center:
return
true
;
case
Alignment.Right:
return
true
;
default
:
return
false
;
}
}
这个方法比起IsDefine方法高效多了。