原文排版格式: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必须提供无参数构造函数,因此无法实现封闭值域。

C# Idioms: Enum还是Enum Class(枚举类)相关推荐

  1. java 枚举类型enum ppt,关于JAVA枚举类使用的异常

    当前位置:我的异常网» J2SE » 关于JAVA枚举类使用的异常 关于JAVA枚举类使用的异常 www.myexceptions.net  网友分享于:2013-01-24  浏览:5次 关于JAV ...

  2. enum 1.0 java 枚举类

    文章目录 枚举类 1. 使用静态常量表示枚举 2. 通过定义类的方式表达枚举 3. 枚举类 4. 枚举类实现接口 每个枚举实例实现自己的方法 5. 包含抽象方法的枚举类 枚举类 在某些情况下,一个类的 ...

  3. java switch枚举类,使用枚举类enum代替switch

    使用枚举类enum代替switch 使用枚举类,能大大减少switch的代码量,提高代码阅读性 public enum TestEnum { stu1("小明", "一班 ...

  4. Java中的枚举类是什么?enum关键字怎么使用?

    枚举类 文章目录 枚举类 枚举类的使用:入门 自定义枚举类 方法一:自定义枚举类 方式二: enum 关键字定义枚举类(主要用该方式) Enum类的主要方法 使用enum关键字定义的枚举类实现接口 主 ...

  5. java枚举类Enum入门理解

    目录 枚举的定义 JDK5.0之前只能自定义枚举类 自定义枚举类的理解: JDK5.0之后enum关键字定义枚举类 区别于自定义枚举类 enum的父类Enum的常用方法 toString方法和valu ...

  6. 枚举类——概述、常用方法、自定义枚举类、Enum创建枚举类

    一.枚举类的概述 1.枚举类的理解:类的对象只有有限个,确定的.我们称此类为枚举类 2.当需要定义一组常量时,强烈建议使用枚举类 3.如果枚举类中只一个对象,则可以作为单例模式的实现方式. 二.枚举类 ...

  7. java switch enum对象_Java枚举类(enum)5种常见用法和3种内部方法,详细附代码

    文章目录 Java枚举类(enum)重点用法和内部方法,附代码 一.Java 枚举类(enum) 详解5种常见的用法 1.常量 2.支持switch 3.向枚举中添加新方法 4.覆盖枚举的方法 5.实 ...

  8. SpringBoot 框架中 使用Spring Aop 、创建注解、创建枚举类 使用过程记录

    1.开始 在Springboot框架中引入AOP <dependency><groupId>org.springframework.boot</groupId>&l ...

  9. 你连简单的枚举类都不知道,还敢说自己会Java???滚出我的公司

    枚举类型是Java 5中新增的特性,它是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性.安全性以及便捷性.当 ...

  10. 学妹问我Java枚举类与注解,我直接用这个搞定她!

    很多人问我学妹长什么样,不多说 上图吧! 学妹问我Java枚举类与注解,我直接一篇文章搞定! 一.枚举类 ① 自定义枚举类 ② enum关键字定义枚举类 ③ enum 枚举类的方法 ④ enum 枚举 ...

最新文章

  1. 如何查看指定端口.(win)
  2. synchronized(二)
  3. 实现instanceof关键字
  4. ARM64的启动过程之(一):内核第一个脚印
  5. codeforce 606A - Magic Spheres
  6. 计算机二级ms office知识点讲解,全国计算机二级MSOffice复习知识点
  7. hive连接mysql报错_hive远程模式初始化mysql报错
  8. Linux进程间通信——使用消息队列
  9. 升级php5.5.10扩展不兼容
  10. JAVA毕设项目vue架构云餐厅美食订餐系统(Vue+Mybatis+Maven+Mysql+sprnig+SpringMVC)
  11. 15日精读掌握《高德纳:具体数学》计划(2019.5/27-2019/6/10)
  12. 手机一键抠图软件哪个好?分享三个好用软件给你
  13. Pycharm工具下的数据可视化(图形绘制)
  14. 数字验证和table格式(KAYAK)
  15. 陈景润定理的数学证明何处寻?
  16. CocosCreator微信小游戏之排行榜
  17. 读书笔记2014第8本:《追寻生命的意义》
  18. WinMain:应用程序入口点【WinMain: The Application Entry Point】
  19. java 打印大小设置_使用QPrinter设置自定义纸张尺寸无法正确打印
  20. 无动物型胶原酶丨Worthington的多种应用方案

热门文章

  1. 计算机二级应用题改卷,全国计算机等级考试二级Python语言程序设计模拟试卷B卷综合应用题-Go语言中文社区...
  2. android studio islibrary,通过AndroidStudio发布Android Library到Jcenter[超详细]
  3. python备份文件代码_python备份文件以及mysql数据库的脚本代码
  4. html 页面自适应窗口大小,JavaScript实现自适应窗口大小的网页
  5. 计算点云之间的平均距离,方差,标准差
  6. html脚本语言居中,web前端:CSS--几种常用的水平垂直居中对齐方法
  7. 在CentOS 6.6 64bit上安装Oracle VirtualBox 5.0.12
  8. ATS 5.3.0中按域名生成日志配置文件log_hosts.config解读
  9. 在虚幻引擎5中重塑火箭联盟——口袋联盟
  10. 对ARM异常(Exceptions)的理解