分享
 
 
 

C# Idioms: Enum还是Enum Class(枚举类)

王朝c#·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

C# Idioms:Enum还是Enum Class(枚举类)

marshine

(原文排版格式:http://www.marshine.com)

reversion:2004/5/28

修改说明:感谢Ninputer提到的CLS兼容问题,同时修改了原来版本没有提及的Equals改写,以及修改"=="重载的不完善代码,和增加enum struct内容

reversion:2004/6/4

增加kirc提到的Enum的Flags特性,因为文本超长,新的版本可以在http://www.marshine.com上阅读。

常量类型的表示

系统中常常有一些属性的属性值是固定的一组值,它们的值域是封闭的(有限数量),比如国家代码(每个国家具有唯一的代码,而在一定时期国家的数量是确定的)、性别类型(男、女)。在现代 程序语言中,一种典型的表示方式是枚举类型(Enum)。Enum表示封闭值域的类型,常常由程序语言作为一种数据类型直接支持,例如C,C#等。C#支持的enum在C的基础上提供了类型安全的能力,下面是用C#定义的性别枚举类型:

public enum Sex {

Male,

Female,

}

Java不支持enum数据类型,Java认为C提供的enum并不是类型安全的,通常使用称之为Typesafe Enum Class的设计模式来获得类似的效果(参见[Joshua01] P80,Item21 :Replace enum constructs with classes)。Enum Class不允许外部构造实例成员(构造函数为private),提供静态类型成员实例来表示封闭值域。使用Enum Class方式来表示Sex类型可定义如下(C#):

public class Sex{

// 私有构造保证值域的封闭性

private Sex() {

}

pubic static readonly Sex Male = new Sex():

pubic static readonly Sex Female = new Sex():

}

同enum一样,可以使用Sex.Male或Sex.Female的方式来访问常量属性,与静态常量字段不一样(如静态字符串、整数),enum和Enum Class可以提供强类型的compile time检查以及提供更好的数据封装性和代码可读性。例如使用常量类型设置和比较属性值:

// 设置属性值

Sex sex = Sex.Male;

// 比较

if (sex == Sex.Male) {

// ... ...

}

如果Sex是使用Enum定义的,则上面比较的实际上是Enum字段的值;如果Sex是使用Enum Class定义的,则比较的是静态实例成员的引用地址,当然也可以使用Equals方法来比较。

虽然Enum Class是来自于Java的设计模式,但在C#中并非没有意义,因为Enum Class提供了比Enum类型更强大的能力。

Enum与Enum Class的比较

Enum与Enum Class均提供了封装常量的能力,都能够实现编译时的强类型检查,使用封闭值域防止非法值。不过,因为实现机制的不同,这两种方式也具有不同的特点。

Enum在C#中是一种值类型(Value Type),其基类型必须是整数类型(如Int16),因此Enum也具有值类型所具有的优点——比引用类型(Reference Type)更高的效率,定义简单。但是其缺点不能实现自定义的行为,无法提供常量更多的属性。

Enum Class就没有这种限制,虽然Enum Class本身并不设计为可以继承,但可以修改基类(System.Object)的行为以提供更加丰富的能力(如修改ToString方法,根据使用者的本地语言输出本地化的国家名称),也可以提供更多的属性 。例如我们提供一个候选的国家列表,除了能显示国家名称外,可以提供国家代码、语言代码信息。

Enum Class的问题

但Enum Class也有它的缺点,上面的设计中Enum Class通过进程内静态成员引用地址相同来进行比较,但是当将一个序列化后的Enum Class实例反序列化后,CLR会创建一个新的实例,从而造成反序列化值不等于序列化前值的现象:

IFormatter formatter = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();

MemoryStream stream = new MemoryStream();

// 序列化Sex.Male的值

formatter.Serialize(stream, Sex.Male);

stream.Seek(0,SeekOrigin.Begin);

// 反序列化

Sex sex = (Sex)formatter.Deserialize(stream);

Console.WriteLine(sex == Sex.Male);

上面的代码将输出false。因此通过引用的方式是有局限性的,在Java中这是一个比较棘手的问题,需要修改反序列化的行为(参看[Joshua01]P171)。C#与Java的实现机制不一样,无法通过修改反序列化的行为来返回同一个常量实例, 但C#提供了操作符重载的能力,我们可以通过重载操作符“==”来解决这个问题,同时为了保持CLS兼容以及与Equals的行为一致,还需要改写Equals方法:

[Serializable]

public class Sex{

// 性别类型名

private string sexName;

// 私有构造保证值域的封闭性

private Sex(string sexName) {

this.sexName = sexName;

}

public static readonly Sex Male = new Sex("Male");

public static readonly Sex Female = new Sex("Female");

// 提供重载的"=="操作符,使用sexName来判断是否是相同的Sex类型

public static bool operator ==(Sex op1, Sex op2) {

if (Object.Equals(op1, null)) return Object.Equals(op2, null);

return op1.Equals(op2);

}

public static bool operator !=(Sex op1,Sex op2) {

return !(op1 == op2);

}

public override bool Equals(object obj) {

Sex sex = obj as Sex;

if (obj == null) return false;

return sexName == sex.sexName;

}

public override int GetHashCode() {

return sexName.GetHashCode ();

}

}

通过操作符重载,不再使用引用地址来比较常量,而是通过值比较(如上面的sexName),因此要求每个常量实例必须具有唯一的标识值。 在不支持操作符重载的语言中,不能使用"=="来比较两个常量值是否相等,而应该使用Equals方法来代替。

Enum Class的设计

Enum Class一般符合下列规则:

私有构造函数,保证外部无法创建类实例(同时也使得类无法继承)。

静态只读实例字段表示常量。

重载操作符"==",保证序列化后的值也能比较相等。当需要在进程间传递(如分布式应用)或需要序列化时,必须实现"=="操作符的重载。

改写Equals方法,保持"=="行为和Equals一致。(改写Equals一般也同时改写GetHashCode方法 )

除此之外,还通常改写ToString方法以提供显示友好的名字,因为Java和.Net都在绑定或显示对象时使用ToString方法(Java中为toString方法)输出作为缺省的对象显示字符串,比如将Sex数组绑定到ListBox或者使用Console.Write输出时。下面的代码改写ToString方法以提供友好显示的输出:

public class Sex{

... ...

public override string ToString() {

return sexName;

}

}

当然我们也可以利用ToString提供本地化支持,返回本地语言的字符串。

Enum Class另外一种常见的职责是提供不同值系统之间的类型转换,如当从数据库中读取值时,利用Parse方法将数据库中值转换为对象系统的常量实例,而在存储时提供方法转换为数据库的值类型:

public class Sex{

... ...

// 根据一个符合指定格式的字符串返回类型实例。

public static Sex Parse(string sexName){

switch (sexName) {

case "Male" : return Male;

... ...

}

}

// 返回数据存储的值。

public string ToDBValue(){

return sexName;

}

}

使用Enum还是Enum Class?

根据Enum和Enum Class的特点,我们可以根据对常量类型的要求决定使用Enum还是Enum Class。

以下场景适合使用Enum:

常量类型用于内部表示,不用于显示名字。

常量值不需要提供附加的属性。例如只需要知道国家代码,而不需要获得国家的其它属性

Enum Class可以适用于更多的场景:

常用于可提供友好信息的类型。如本地化支持的类型名显示,或者显示与枚举名不一致的名字,例如Country.CHN可显示为"China"。

提供更多的常量属性。

提供更加丰富的行为。如Parse方法。

对常量进行分组。如Country.Asia包含亚洲国家。

使用Struct来表示枚举

如果值域不封闭,但希望提供一些常量,也可以使用struct,如System.Drawing.Color结构中的系统默认颜色设置。采用struct来设计enum值同Enum Class方式没有本质的差异,只是struct必须提供无参数构造函数,因此无法实现封闭值域。

参考:

[Joshua01]

Effective Java Programming Language Guide , Joshua Bloch, Pearson Education,2001.

Java 高效编程指南(中文版),机械工业出版社,2002

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有