当前位置: 首页 > 编程日记 > 正文

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

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

相关文章:

构建第三代人工智能核心能力,清华、阿里、RealAI等联合发布最新AI安全评估平台

科技是发展的利器,也可能成为风险的源头。近日,张钹院士在智源大会上表示,AI的发展带来了科技是发展的利器,也可能成为风险的源头。近日,张钹院士在智源大会上表示,AI的发展带来了新的风险和安全隐患。 在…

Java 事件响应

按钮按钮(JButton)在界面设计中用于激发动作事件。按钮可显示文本,当按钮被激活时,能激发动作事件。JButton常用构造方法有:JButton():创建一个没有标题的按钮对象;JButton(String s):创建一个标题为s的按钮…

C# Idioms: Safely方法

(原文排版格式 http://www.marshine.com) 名称 Safely Method 意图 通过方法保证返回有效(不为空引用,null或Nothing)的对象或抛出异常,当存在多个调用者时简化调用者需要处理null返回值的代码。 动机 一个存放对象的集合或类似功…

Akka的Actor编程

2019独角兽企业重金招聘Python工程师标准>>> ActorSystem(“companyname”) 相当于注册一家公司一样,负责: 通用配置 如:dispatchers, deployments, remote capabilities and addresses 创建Actor和搜索actor 通常一个应用一个…

干货!机器学习中,如何优化数据性能

作者 | 中国农业银行研发中心 张梓聪出品 | AI 科技大本营(ID:rgznai100)头图 | 下载于视觉中国得益于覆盖各种需求的第三方库,Python在今天已经成为了研究机器学习的主流工具。不过由于其解释型语言的特性,在运行速度上往往和传统…

JavaScript深入理解对象方法——Object.entries()

Object.entries() Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环也枚举原型链中的属性)。 语法 Object.entries(obj) 参数 obj可以返回其可枚…

C#非对称加密程序

using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.Data; using System.IO; using System.Text; using System.Security.Cryptography; namespace 非对称加密 { /// <summa…

Exchange Server2013 系列十:证书的配置

Exchange Server2013 系列十&#xff1a;证书的配置杜飞经过前面的配置&#xff0c;基本上可以进行简单的邮件通讯了&#xff0c;但是当用户通过OWA连接邮箱时会报下面的提示&#xff1a;其他一些服务&#xff0c;如 Outlook Anywhere 和 Exchange ActiveSync&#xff0c;也要求…

高级程序员到底高级在哪里?

身为一名技术人&#xff0c;你是否遇到过这些情况&#xff1f;工作效率低&#xff1a;别人1小时就能修复的bug&#xff0c;你需要3小时没有存在感&#xff1a;技术趋势看不透&#xff0c;和同事聊天完全插不上话技术提升慢&#xff1a;苦熬996&#xff0c;但升职加薪仍然遥遥无…

AlexNet 网络详解及Tensorflow实现源码

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 1. 图片数据处理2. 卷积神经网络 2.1. 卷积层2.2. 池化层2.3. 全链层3. AlexNet4. 用Tensorflow搭建完整的AlexNet5. 用AlexNet识别猫狗图片 5.1. 定义分类5.2. 训练网络5.3. 验证1. 图片数据处理 一…

.net反射详解(转)

摘自&#xff1a;http://www.cnblogs.com/knowledgesea/archive/2013/03/02/2935920.html 概述反射 通过反射可以提供类型信息&#xff0c;从而使得我们开发人员在运行时能够利用这些信息构造和使用对象。 反射机制允许程序在执行过程中动态地添加各种功能。 运行时类型标识 …

C# 多网卡 Server Listen

VC和BCB中做一个Server的监听程序,只需要指定端口,然后监听(Listen)就行了.在C#找不到这个函数了,慢慢看MSDN,怎么需要指定IP和Port才能监听,那么多网卡的机器应该怎么写程序呢?下面的程序可以解释怎么去做. TcpListener 类别会提供简易的方法&#xff0c;用以在封锁的同步模式…

赠书 | 一文了解预训练语言模型

来源 | 博文视点头图 | 下载于视觉中国近年来&#xff0c;在深度学习和大数据的支撑下&#xff0c;自然语言处理技术迅猛发展。而预训练语言模型把自然语言处理带入了一个新的阶段&#xff0c;也得到了工业界的广泛关注。通过大数据预训练加小数据微调&#xff0c;自然语言处理…

写了六个相同功能的函数之后,我学到了什么

本文讲的是写了六个相同功能的函数之后&#xff0c;我学到了什么&#xff0c;几周之前&#xff0c;一个社区在 Free Code Camp’s Forum 上发起了非官方的算法大赛。 这个题目看似很简单&#xff1a;返回小于数字 N 的所有 3 或者 5 的倍数的和&#xff0c;N 是函数的参数。 但…

libevent介绍

libevent是一款事件驱动的网络开发包 由于采用 c 语言开发 体积小巧&#xff0c;跨平台&#xff0c;速度极快。 通常我们在建立服务器的处理模型的时候,主要是下面集中模型;(1) a new Connection 进来&#xff0c;用 fork() 产生一个 Process 处理。 (2) a new Connecti…

蓝色起源载人火箭7月首飞,贝索斯即将实现儿时愿望

整理 | 寇雪芹出品 | AI 科技大本营&#xff08;ID:rgznai100&#xff09;头图 | 下载于ICphoto美国当地时间6月7日早&#xff0c;亚马逊创始人、世界首富贝索斯&#xff08;Jeff Bezos&#xff09;在社交媒体上发帖表示&#xff0c;自己将在7月20日乘坐蓝色起源&#xff08;Bl…

使用jquery.more.js来实现点击底部更多后, 底部加载出新的数据

<div class"bus-nav-bar ft12"><div class"navt bor-r-c pos-rel {if $int 0}fwbold{/if}"><a href"portal.php?modmerchant&actionvoucherlist&int0">全部订单</a><em class"pos-abs"></…

ios开发学习-手势交互(Gesture)效果源码分享

qianqianlianmengios开发学习-手势交互&#xff08;Gesture&#xff09;效果源码分享 All Around Pull View 介绍&#xff1a;实现视图四个方向&#xff08;上下左右&#xff09;都能够拖动更新&#xff08;pull to refresh&#xff09;。 编译测试&#xff0c;测试环境…

通过C#实现集合类纵览.NET Collections及相关技术

概述&#xff1a;在真正的对象化开发项目中&#xff0c;我们通常会将常用的业务实体抽象为特定的类&#xff0c;如Employee、Customer、Contact等&#xff0c;而多数的类之间会存在着相应的关联或依存关系&#xff0c;如Employee和Customer通过Contact而产生关联、Contact是依赖…

TIOBE 6 月榜单: Python 有望超越 C 语言成为第一名

整理 | 苏宓出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;头图 | 下载于ICphotoTIOBE 官方最新发布了 6 月的编程语言榜单&#xff0c;这个月榜单中又有怎样的发展趋势&#xff1f;Python 有望成为第一名在本月榜单中&#xff0c;位居第二名的 Python 与第一名 C…

使用dom4j解析XML例子

包括三个文件&#xff1a;studentInfo.xml(待解析的xml文件), Dom4jReadExmple.java(解析的主要类), TestDom4jReadExmple.java(测试解析的结果) 代码运行前需先导入dom4j架包。 studentInfo.xml文件&#xff08;该文件放在本项目目录下&#xff09;内容如下&#xff1a; <?…

mkdir、rmdir命令、head、tail命令

mkdir-p 递归创建目录11里面都是空目录rmdir删除空目录 -p 当子目录被删除后使它也成为空目录的话&#xff0c;则一并删除步骤&#xff1a;先删除11/22/33 发现22目录空了&#xff0c;因为33删了&#xff0c;于是再删22&#xff0c;空了再删11head默认是前10行 –n指定几行tai…

Linux Find 命令精通指南

作者&#xff1a;Sheryl Calish Linux find 命令是所有 Linux 命令中最有用的一个&#xff0c;同时也是最混乱的一个。它很难&#xff0c;因为它的语法与其他 Linux 命令的标准语法不同。但是&#xff0c;它很强大&#xff0c;因为它允许您按文件名、文件类型、用户甚至是时间戳…

【安全运维】 linux 系统账户,网络,简易安全加固方案(第一部分),经测试可行...

前言讲到linux系统账户的管理以及安全&#xff0c;就必须涉及 /etc/passwd /etc/shadow 这2个文件这里以截图中文字说明的方式&#xff0c;来分析这2个文件的内容&#xff0c;并且给出一些实用的安全加固方案注意&#xff0c;本文会持续更新&#xff0c;后续加入的内容都以直…

不用深度学习,怎么提取图像特征?

来源 | 小白学视觉头图 | 下载于ICphoto图像分类是数据科学中最热门的领域之一&#xff0c;在本文中&#xff0c;我们将分享一些将图像转换为特征向量的技术&#xff0c;可以在每个分类模型中使用。VATboxVATbox&#xff0c;作为n一个我们所暗示的&#xff0c;涉及增值税问题&a…

课程第五天内容《基础交换 五》

2019独角兽企业重金招聘Python工程师标准>>> 以太网/LAN(local area network )的相关概念&#xff1a; 问题&#xff1a; 信号传输距离有限&#xff1b; 解决方案&#xff1a; 中继器 - 放大电信号&#xff0c;延长信息的传输距离&#xff1…

C#综合揭秘——Entity Framework 并发处理详解

引言 在软件开发过程中&#xff0c;并发控制是确保及时纠正由并发操作导致的错误的一种机制。从 ADO.NET 到 LINQ to SQL 再到如今的 ADO.NET Entity Framework&#xff0c;.NET 都为并发控制提供好良好的支持方案。 并发处理方式一般分为乐观必并发与悲观必并发两种&#xff0…

@2021高考生,用 Python 分析专业“钱景”

来源 | 关于数据分析于可视化头图 | 下载于ICphoto2021年的高考在昨日拉开帷幕&#xff0c;十年的寒窗苦读&#xff0c;终于到了最后见分晓的时候了。在这么一场关键的考试当中&#xff0c;除了考试前努力奋斗&#xff0c;考场上认真答题&#xff0c;考后的志愿填报也是极其的重…

Linux下C语言的fgets与fputs

使用的是 CentOS gcc编译下面程序 显示warning: the gets function is dangerous and should not be used.问题出在程序中使用了 gets Linux 下gcc编译器不支持这个函数&#xff0c;解决办法是使用 fgets fgets()函数的基本用法为&#xff1a; fgets(char * s,int size,FILE * …

linux发行版的用户交互

1 cli&#xff0c;即command line interface 纯命令行的交互方式&#xff0c;该命令行界面是由shell提供的。 linux内核本身也自带了一个console&#xff0c;即linux console&#xff0c;它是基于frame buffer的。 cli的界面都是基于ncurses库开发的。 2 GUI&#xff0c;graphi…