C#一探究竟——枚举
枚举是值类型而System.Enum却是引用类型的原因
Q:在C#里,我们如何表达枚举类型?
A:你可以使用enum关键字(keyword)来声明一个枚举类型(enum type):
- // Code #01
- public enum Alignment
- {
- Left,
- Center,
- Right
- }
--------------------------------------------------------------------------------
Q:C#枚举类型是值类型(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
- .class public auto ansi sealed Aligment
- extends [mscorlib]System.Enum
- {
- .field public static literal Aligment Left = int32(0x00000000)
- .field public static literal Aligment Center = int32(0x00000001)
- .field public static literal Aligment Right = int32(0x00000002)
- .field public specialname rtspecialname int32 value__
- }
从声明中,你可以看到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的声明:
- public abstract class Enum : ValueType, IComparable, IFormattable, IConvertible
请注意,.NET Framework SDK v2.0.3600.0 Documentation中的Enum声明是错的:
public abstract struct Enum : IComparable, IFormattable, IConvertible
--------------------------------------------------------------------------------
Q:开始头晕了,究竟C#枚举类型、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
- static void Main()
- {
- Type t = typeof(System.Enum);
- if (t.IsEnum)
- Console.WriteLine("I'm enum type.");
- if (t.IsValueType)
- Console.WriteLine("I'm value type.");
- }
请别惊讶于程序的运行结果没有任何输出!对于第一个判断,我们很清楚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
- // See Code #01 for Alignment.
- static void Main()
- {
- Alignment a = Alignment.Center;
- Console.WriteLine(a.ToString());
- Console.WriteLine(a);
- }
对应的IL代码是:
- // Code #06
- .method private hidebysig static void Main() cil managed
- {
- .entrypoint
- // Code Size: 32 byte(s)
- .maxstack 1
- .locals (
- EnumerationFaq.Alignment alignment1)
- L_0000: ldc.i4.1
- L_0001: stloc.0
- L_0002: ldloc.0
- L_0003: box EnumerationFaq.Alignment
- L_0008: call instance string [mscorlib]System.Enum::ToString()
- L_000d: call void [mscorlib]System.Console::WriteLine(string)
- L_0012: nop
- L_0013: ldloc.0
- L_0014: box EnumerationFaq.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
- // See Code #01 for Alignment.
- class Program
- {
- static void Main()
- {
- Alignment a = Alignment.Center;
- Print(a);
- }
- static void Print(IConvertible c)
- {
- Console.WriteLine(c);
- }
- static void Print(IFormattable f)
- {
- Console.WriteLine(f);
- }
- static void Print(IComparable c)
- {
- Console.WriteLine(c);
- }
- static void Print(Object o)
- {
- Console.WriteLine(o);
- }
- static void Print(ValueType v)
- {
- Console.WriteLine(v);
- }
- static void Print(Enum e)
- {
- Console.WriteLine(e);
- }
- }
上面的代码将被编译成如下的IL:
- // Code #08
- .method private hidebysig static void Main(string[] args) cil managed
- {
- .entrypoint
- // Code Size: 15 byte(s)
- .maxstack 1
- .locals (
- EnumerationFaq.Alignment alignment1)
- L_0000: ldc.i4.1
- L_0001: stloc.0
- L_0002: ldloc.0
- L_0003: box EnumerationFaq.Alignment
- // 调用static void Print(Enum e);
- L_0008: call void EnumerationFaq.Program::Print([mscorlib]System.Enum)
- L_000d: nop
- L_000e: ret
- }
C#一探究竟——枚举的更多相关文章
- 很多人都搞不清楚C语言和C++的关系!今天我们来一探究竟为大家解惑~
最近,身边有许多小伙伴已经开始学习编程了,但是呢,学习又会碰到许多的问题,其中作为新手小白提到最多的问题就是编程语言的选择. 每次遇到这种问题,看起来很简单,但是又有很多小伙伴搞不清编程语言之间的关系 ...
- 并发王者课-青铜5:一探究竟-如何从synchronized理解Java对象头中的锁
在前面的文章<青铜4:synchronized用法初体验>中,我们已经提到锁的概念,并指出synchronized是锁机制的一种实现.可是,这么说未免太过抽象,你可能无法直观地理解锁究竟是 ...
- 打开order by的大门,一探究竟《死磕MySQL系列 十二》
在日常开发工作中,你一定会经常遇到要根据指定字段进行排序的需求. 这时,你的SQL语句类似这样. select id,phone,code from evt_sms where phone like ...
- .NET中的枚举(Enum)
摘要:.NET中的枚举分为简单枚举和标志枚举,这次主要总结一下标志枚举适用条件,以及它的使用方法,并在文章的最后列举枚举使用的一些规范. 在刚接触.NET的枚举时,只用简单的枚举,对于标记枚举,只知道 ...
- gulp.src()内部实现探究
写在前面 本来是想写个如何编写gulp插件的科普文的,突然探究欲又发作了,于是就有了这篇东西...翻了下源码看了下gulp.src()的实现,不禁由衷感慨:肿么这么复杂... 进入正题 首先我们看下g ...
- Java中的枚举--Enumeration
之前并没有注意到枚举这个知识点,因为之前在项目中并没有使用过枚举,可能是项目并不是很复杂的原因吧,今天看张孝祥老师的讲解,觉得,这个枚举真的有很多值得学习的地方,探究一下枚举的设计原理,底层到底是怎么 ...
- [c#基础]关于const和readonly常见的笔试题剖析
引言 有那么几天没更新博客了,发现到了不得不写的地步,总是有那么个声音在强迫自己,虽然工作很累,但是有些东西不写出来,不能原谅自己.今天为什么总结这两个关键字的区别,总觉得这两个关键字的用法用的太习惯 ...
- 【转】Logistic regression (逻辑回归) 概述
Logistic regression (逻辑回归)是当前业界比较常用的机器学习方法,用于估计某种事物的可能性.比如某用户购买某商品的可能性,某病人患有某种疾病的可能性,以及某广告被用户点击的可能性等 ...
- SpringBoot学习之自动依赖
在前面使用SSM集成时,我们可以使用注解实现无配置化注入,但是这种依赖被进行“人工干预了的”,换句话就是说我们手动进行装配,那么此时还没有达到SpringBoot这种自动装配的效果,那么究竟Sprin ...
随机推荐
- linux系统CentOS7
linux系统CentOS7 到http://mirrors.sohu.com/mysql/下载想要的mysql版本 这里用到的是 mysql-5.6.33-linux-glibc2.5-x86_64 ...
- 【第二课】WEBIX 入门自学-获取WEBIX及相关资料
下载WEBIX组件库 http://webix.com/download/ WEBIX在线文档 http://docs.webix.com/ 离线的手册 下载
- 经典排序算法 – 插入排序Insertion sort
经典排序算法 – 插入排序Insertion sort 插入排序就是每一步都将一个待排数据按其大小插入到已经排序的数据中的适当位置,直到全部插入完毕. 插入排序方法分直接插入排序和折半插入排序两种, ...
- SPRING SECURITY JAVA配置:Web Security
在前一篇,我已经介绍了Spring Security Java配置,也概括的介绍了一下这个项目方方面面.在这篇文章中,我们来看一看一个简单的基于web security配置的例子.之后我们再来作更多的 ...
- python RecursionError: maximum recursion depth exceeded in comparison错误
处理快速排序,递归深度可能非常大,而系统默认的深度可能没有这么大 需要设置最大递归深度 import sys sys.setrecursionlimit(100000) # 这个值的大小取决你自己,最 ...
- 如何去掉dede列表推荐时标题被加粗
dede在列表推荐文章默认为加粗不清楚的可以看图: 那个加黑的是默认的.如果你不想要被加黑,可以做如下改动.在include里找到文件:arc.listview.class.php查找并删除(注释掉也 ...
- 【LabVIEW技巧】路径依赖解除方法
前言 LabVIEW程序开发,让我们的程序设计变的简单容易,但是设计过程中也不乏大量的重复性工作,其中最让人头痛的莫过于依赖冲突问题. 事实上,只要你对文件进行了修改或者移动,必不可少的依赖冲突就会产 ...
- *HDU1846HDU2188 巴什博奕
Brave Game Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total ...
- 关于PHP中<?php ?>的结束标签是否添加
在纯PHP代码中“?>”结束标签最好不要添加 原因:容易导致输出多余的空白或者换行,以及由此产生的一些报错. 比如a.php文件中,在?>标签后面多出空格或者换行,而当b.php文件引入a ...
- USACO翻译:USACO 2012 JAN三题(1)
USACO 2012 JAN(题目一) 一.题目概览 中文题目名称 礼物 配送路线 游戏组合技 英文题目名称 gifts delivery combos 可执行文件名 gifts delivery c ...