`
lovnet
  • 浏览: 6883071 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

关于枚举的种种 (Enumeration FAQ) [C#, IL, BCL]

阅读更多

关于枚举的种种 [C#, IL, BCL]

Enumeration FAQ [C#, IL, BCL]

Updated on Tuesday, January 11, 2005

Written by Allen Lee

Q:在C#里,我们如何表达枚举类型?

A:你可以使用enum关键字(keyword)来声明一个枚举类型(enum type):

//Code#01
publicenumAlignment
{
Left,
Center,
Right
}


Q:枚举类型是值类型(value type)还是引用类型(reference type)?

A:枚举类型都是值类型。


Q:System.Enum是枚举类型么?

A:不是。


Q:System.Enum与枚举类型(enum type)有什么关系?

A:System.Enum是一个抽象类(abstract class),所有枚举类型都直接继承自它,当然也同时继承了它的所有成员。


Q:那么System.Enum属于引用类型啦?

A:是的。


Q:既然System.Enum是引用类型,而枚举类型又是直接继承自System.Enum的,那为什么枚举类型却不是引用类型?

A:这种继承关系是隐式的并由编译器负责展开,上面Code #1的Alignment枚举被展开后的IL代码如下:

//Code#02
.classpublicautoansisealedAligment
extends[mscorlib]System.Enum
{
.field
publicstaticliteralAligmentLeft=int32(0x00000000)
.field
publicstaticliteralAligmentCenter=int32(0x00000001)
.field
publicstaticliteralAligmentRight=int32(0x00000002)

.field
publicspecialnamertspecialnameint32value__
}

从声明中,你可以看到Aligment的确是继承自System.Enum的,只是你不能在C#里显式声明这种继承关系。


Q:但你好像没有回答为什么枚举类型继承自一个引用类型后,却还是值类型!

A:你知道,所有的值类型都是System.ValueType的后代,枚举类型也不例外,枚举类型直接继承自System.Enum,而System.Enum却又直接继承自System.ValueType的,所以,枚举类型也是System.ValueType的后代。


Q:慢着!从System.ValueType派生出来的类型不都应该是值类型吗?为什么System.Enum会是引用类型?

A:正确的说法应该是“值类型都是System.ValueType的后代”,但System.ValueType的后代不全是值类型,System.Enum就是唯一的特例!在System.ValueType的所有后代中,除了System.Enum之外其它都是值类型。事实上,我们可以在.NET的源代码中找到System.Enum的声明:

publicabstractclassEnum:ValueType,IComparable,IFormattable,IConvertible

请注意,.NET Framework SDK v2.0.3600.0 Documentation中的Enum声明是错的:

public abstract struct Enum : IComparable, IFormattable, IConvertible


Q:开始头晕了,究竟枚举类型、System.Enum、System.ValueType、值类型和引用类型之间存在着什么样的关系?

A:简单的说,

  • 1. 所有枚举类型(enum type)都是值类型。
  • 2. System.Enum和System.ValueType本身是引用类型。
  • 3. 枚举类型(enum type)都是隐式的直接继承自System.Enum,并且这种继承关系只能由编译器自动展开。但System.Enum本身不是枚举类型(enum type)。
  • 4. System.Enum是一个特例,它直接继承自System.ValueType(参见Code #03),但本身却是一个引用类型。

好吧,现在来看看下面代码,你能猜得出它的输出结果吗?

//Code#04
staticvoidMain()
{
Typet
=typeof(System.Enum);

if(t.IsEnum)
Console.WriteLine(
"I'menumtype.");

if(t.IsValueType)
Console.WriteLine(
"I'mvaluetype.");
}

请别惊讶于程序的运行结果没有任何输出!对于第一个判断,我们很清楚System.Enum并不是枚举类型。但第二个判断呢?System.Enum明明继承自System.ValueType,却不承认是System.ValueType的后代!这是.NET上的一个特例,恰恰体现出System.Enum是特殊性。


Q:既然枚举类型是值类型,自然会涉及到装箱和拆箱(boxing and unboxing)的问题,那么枚举类型会被装箱成什么呢?[Updated]

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。对应的装箱操作既可以为隐式的也可以是显式的。

下面的C#代码:

//Code#05
//SeeCode#01forAlignment.
staticvoidMain()
{
Alignmenta
=Alignment.Center;

Console.WriteLine(a.ToString());

Console.WriteLine(a);
}

对应的IL代码是:

//Code#06
.methodprivatehidebysigstaticvoidMain()cilmanaged
{
.entrypoint
//CodeSize:32byte(s)
.maxstack1
.locals(
EnumerationFaq.Alignmentalignment1)
L_0000:ldc.i4.1
L_0001:stloc.0
L_0002:ldloc.0
L_0003:boxEnumerationFaq.Alignment
L_0008:callinstance
string[mscorlib]System.Enum::ToString()
L_000d:call
void[mscorlib]System.Console::WriteLine(string)
L_0012:nop
L_0013:ldloc.0
L_0014:boxEnumerationFaq.Alignment
L_0019:call
void[mscorlib]System.Console::WriteLine(object)
L_001e:nop
L_001f:ret
}

从IL代码中我们可以看到枚举类型被装箱两次。第一次(L_0003)被装箱成System.Enum,而第二次(L_0014)就被装箱成System.Object。

但如果你让编译器自动为你选择装箱类型的话,它会优先考虑System.Enum:

//Code#07
//SeeCode#01forAlignment.
classProgram
{
staticvoidMain()
{
Alignmenta
=Alignment.Center;

Print(a);
}


staticvoidPrint(IConvertiblec)
{
Console.WriteLine(c);
}


staticvoidPrint(IFormattablef)
{
Console.WriteLine(f);
}


staticvoidPrint(IComparablec)
{
Console.WriteLine(c);
}


staticvoidPrint(Objecto)
{
Console.WriteLine(o);
}


staticvoidPrint(ValueTypev)
{
Console.WriteLine(v);
}


staticvoidPrint(Enume)
{
Console.WriteLine(e);
}

}

上面的代码将被编译成如下的IL:

//Code#08
.methodprivatehidebysigstaticvoidMain(string[]args)cilmanaged
{
.entrypoint
//CodeSize:15byte(s)
.maxstack1
.locals(
EnumerationFaq.Alignmentalignment1)
L_0000:ldc.i4.
1
L_0001:stloc.
0
L_0002:ldloc.
0
L_0003:boxEnumerationFaq.Alignment
//调用staticvoidPrint(Enume);
L_0008:callvoidEnumerationFaq.Program::Print([mscorlib]System.Enum)
L_000d:nop
L_000e:ret
}


Q:我留意到Code #02中的

.fieldpublicstaticliteralAligmentCenter=int32(0x00000001)

该语句明显是整数赋值,这是否说明枚举类型实质上是整数类型?

A:这说明枚举类型与整数类型的确有一定的关系。事实上,每一个枚举类型都有与之相对应的整数类型,我们称该整数类型为底层类型(underlying type),默认的情况下使用,.NET使用System.Int32。当然,你可以手动将其指定为其他的整数类型:

//Code#09
publicenumAlignment: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”推入堆栈。事实上,底层类型说明了如何为枚举类型分配空间,不同的底层类型所占用的资源不同,大概当你在受限系统上进行开发的话,你就可能需要注意一下了。


Q:枚举成员的值是怎样规定的?

A:如果你没有手动指定成员的值的话,从上往下看,各成员的值为:0, 1, 2, ...。说罢了,就是一个非负整数等差数列,其初值为0,步长为1。例如:

//Code#10
publicenumAlignment
{
Left,
//0
Center,//1
Right//2
}


Q:如果我有手动指定某些成员的值呢?

A:那么被赋值的成员的值就是你所指定的值。当然,无论你是否手动指定枚举成员的值,递增步长都不会变,总是为1。为了测试你是否理解,请说出下面枚举个成员的值以及你的判断理由(请用人脑而不是电脑来运行以下代码):

//Code#11
publicenumDriveType:sbyte
{
CDRom,
Fixed
=-2,
Network,
NoRootDirectory
=-1,
Ram,
Removable
=Network*NoRootDirectory,
Unknown
}


Q:我们如何获取枚举成员的值,无论成员是否被手动赋值?

A:你可以使用System.Enum的

publicstaticArrayGetValues(TypeenumType);

该方法返回一个包含所有枚举成员的数组:

//Code#12
//SeeCode#01forAlignment.
publicstaticvoidMain()
{
Alignment[]alignments
=(Alignment[])Enum.GetValues(typeof(Alignment));
Console.WriteLine(
"WannaseethevaluesofAlignment'smenbers?");
foreach(Alignmentainalignments)
Console.WriteLine(
"{0:G}={0:D}",a);
}


//Output:
//WannaseethevaluesofAlignment'smenbers?
//Left=0
//Center=1
//Right=2


Q:如果我只需要其中某些枚举成员的值呢?

A:那么你可以把枚举转换为IConvertible接口,再调用对应的方法:

//Code#12
//SeeCode#01forAlignment.
publicstaticvoidMain()
{
IConvertibleic
=(IConvertible)Alignment.Center;
inti=ic.ToInt32(null);
Console.WriteLine(
"ThevalueofAlignment.Centeris{0}.",i);
}


//Output:
//ThevalueofAlignment.Centeris1.


Q:为什么需要手动指定枚举成员的值?

A:一般情况下,使用默认的赋值规则就足够了,但某些情况下,为枚举成员指定一个与实际情况(模型)相符的值可能更有意义,这要视你具体所建的模型而定。

还是让我们来一个实际的例子:

//Code#13
publicenumCustomerKind
{
Normal
=90,
Vip
=80,
SuperVip
=70,
InActive
=100
}


publicclassCustomer
{
publicreadonlyCustomerKindKind;

privatedoublem_Payment;
publicdoublePayment
{
returnm_Payment*(int)Kind/100;
}


//Codehere
}

我为枚举CustomerKind的每个成员都赋了一个特定的值,该值其实就是顾客购物折扣百分率。而在Customer类中,Payment属性就通过强类型转换来获取枚举成员的值(也就是购物折扣率),并用于货款计算。从这里可以看出,获取枚举成员的值还可以通过强类型转换方式。


Q:既然枚举类型可以强制转换为整数,那么整数是否也可以强制转换为枚举类型?

A:答案是肯定的。

//Code#14
//SeeCode#01forAlignment.
Alignmenta=(Alignment)1;

但这种机制可能使你遇到一些麻烦:

//Code#15
//SeeCode#01forAlignment.
classProgram
{
staticvoidMain()
{
Foo((Alignment)
12345);
}


staticvoidFoo(Alignmenta)
{
//Codehere
}

}

你无法避免有人进行这样的恶作剧!!


Q:那么是否有办法对付这些恶作剧的人?

A:Sure!我们总不能假设人人都那么守规矩,所以,我们需要System.Enum的

publicstaticboolIsDefined(TypeenumType,objectvalue);

现在我们把Code #15的Foo方法改进一下:

//Code#16
//SeeCode#01forAlignment.
staticvoidFoo(Alignmenta)
{
if(!Enum.IsDefined(typeof(Alignment),a))
thrownewArgumentException("DONOTMAKEMISCHIEF!");

//Codehere
}

这样,恶作剧的人将会收到一个警告(异常消息)。当然,我们不排除有人是由于一时大意才造成这样的“恶作剧”,那么IsDefined方法同样可以帮助你处理好这些情况。


Q:我认为我们还可以使用条件判断语句来处理这种情况:

//Code#17
//SeeCode#01forAlignment.
staticvoidFoo(Alignmenta)
{
if(a!=Alignment.Left&&<span s
分享到:
评论

相关推荐

    PCI设备信息枚举(Enumeration)

    Here,i give a program traversing PCI Device Configuration Space,pl throw them out to me if you have any questions about rationale.

    C#枚举本地计算机硬件设备

    在C#编程中,枚举(Enumeration)是一种特殊的数据类型,通常用来定义一组相关的命名常量。在这个场景中,"C#枚举本地计算机硬件设备"指的是使用C#语言来获取并列举本地计算机上的硬件设备资源。这涉及到操作系统...

    C#中的枚举类型:定义、使用与最佳实践

    在C#编程语言中,枚举(Enumeration)是一种特殊的值类型,它允许开发者为一组相关的常量定义一个名称。枚举提供了一种更加清晰和易于理解的方式来处理固定的常量集合,这在表示选项、状态、模式等场景中尤其有用。...

    USB枚举过程 USB Enumeration

    USB枚举(USB Enumeration)是USB设备接入系统时的一个关键步骤,它允许主机识别和配置新连接的USB设备。枚举过程确保了设备能够与系统正确交互,从而实现数据传输和功能启用。 #### 枚举过程的重要性 USB枚举过程...

    C# USB Enumeration:C#枚举USB返回集线器和端口号-开源

    在给定的标题“C# USB Enumeration:C#枚举USB返回集线器和端口号-开源”中,我们可以理解这是一个关于使用C#语言实现USB设备枚举的项目,它的主要功能是获取USB设备连接到计算机时所在的集线器和端口号。这个项目还...

    C# 枚举PC设备的源代码

    在C#编程中,枚举(Enumeration)是一种特殊的数据类型,用于定义一组具有命名常量的集合。这些常量代表了特定的值,通常用于简化和增强代码的可读性。在“C# 枚举PC设备”的上下文中,枚举可能被用来表示不同类型的...

    C# 枚举正在运行的进程源码

    在C#编程中,枚举(Enumeration)是一种特殊的数据类型,它允许我们为整数常量定义一组有意义的名称。枚举通常用于简化代码,提高可读性,并且在处理固定集合的值时非常有用。在给定的“C# 枚举正在运行的进程源码”...

    C# 枚举计算机上的进程

    在C#编程中,枚举(Enumeration)是一种特殊的数据类型,用于定义一组具有特定值的常量。在本文中,我们将深入探讨如何使用C#枚举来获取并管理计算机上运行的进程信息。这个主题主要涉及到.NET框架中的System....

    通过表格中数据自动生成枚举和方法(内涵使用教程)

    首先,我们要理解枚举(Enumeration)在编程中的作用。枚举是一种特殊的类,它定义了一组命名的常量,这些常量通常表示特定的值或状态。在C#中,枚举可以用来简化代码,使得代码更具可读性和可维护性。例如,如果你...

    C#枚举系统安装的所有打印机教程

    首先,我们需要理解枚举(Enumeration)在C#中的概念。枚举是一种特殊的类型,它定义了一组命名的常量,这些常量共享相同的基类型,如int或string。在枚举打印机的例子中,我们不会创建枚举来表示打印机本身,而是会...

    56个民族枚举类

    标题中的“56个民族枚举类”指的是在编程中创建的一个枚举(Enumeration)类,用于表示中国56个不同的民族。枚举是面向对象编程中的一种数据类型,它允许我们定义一组有限的常量,这些常量代表了特定的值。在这个...

    详解Java中的迭代迭代器Iterator与枚举器Enumeration

    Java中的迭代器(Iterator)与枚举器(Enumeration)是两种用于遍历集合(Collection)的接口。在Java集合框架中,集合提供了多种数据结构存储对象,而迭代器与枚举器提供了访问这些集合中元素的方法。尽管它们的...

    枚举所有QQ窗口(C#调API)

    在编程领域,枚举(Enumeration)是一种特殊的数据类型,它允许我们定义一组具有特定值的常量。在本文中,我们将深入探讨如何使用C#语言通过调用API(应用程序编程接口)来枚举所有正在运行的QQ窗口。C#本身并不直接...

    如何在C#中对枚举名称进行排序(升序或降序)

    在C#编程中,枚举(Enum)是一种强大的工具,用于定义一组相关的命名常量。在某些场景下,我们可能需要对枚举的名称进行排序,例如在数据绑定或者显示枚举值的列表时。本篇文章将深入探讨如何在C# 4.0中对枚举名称...

    深入理解C#中的枚举

    在C#编程语言中,枚举(Enumeration)是一种非常有用的数据类型,它允许我们为一组相关的整数值定义命名常量。通过使用枚举,代码的可读性和可维护性得到显著提升,因为我们可以使用有意义的名称代替硬编码的数字。 ...

    详细了解C# 枚举与位枚举

     C# 枚举(Enum), 枚举类型是用于声明一组命名的常数的基本数据类型(值类型); 二、枚举的定义:  声明enum变量: enum &lt;enum&gt; {enumeration list};  其中enum_name 指定枚举的类型名称; enumeration list ...

    C# 分类枚举指定计算机的服务

    在C#编程中,枚举(Enumeration)是一种特殊的数据类型,用于定义一组具有特定值的命名常量。在本文中,我们将深入探讨如何使用C#来分类枚举指定计算机上的服务,并了解相关的重要知识点。 首先,我们需要理解...

    java 通过反射获取枚举类,及枚举类的值,枚举类枚举实例名

    枚举(Enumeration)是Java中的一个特殊类类型,用于定义一组常量。本项目"test-enum-demo-master"显然是一个用于演示如何通过反射来操作枚举类的示例。 首先,让我们理解枚举类的基本概念。枚举类在Java中用于定义...

    枚举类型的使用

    在编程语言中,枚举(Enumeration)是一种非常实用的数据类型,尤其在C#中,它提供了对一组相关常量的封装,使得代码更加清晰、易读。本代码笔记主要探讨了C#中的枚举类型及其在实际编程中的应用,特别强调了枚举...

    C# 获取枚举值的简单实例

    在C#编程语言中,枚举(Enumeration)是一种强大的工具,用于定义一组命名的常量。这些常量在程序中代表特定的值,通常用于简化和增强代码的可读性。在给定的实例中,我们将探讨如何在C#中获取枚举值。 首先,我们...

Global site tag (gtag.js) - Google Analytics