它是如何从一个枚举派生System.Enum是一个整数在同一时间?

编辑 :底部的评论。 另外, 这个 。


这是什么让我困惑。 我的理解是,如果我有这样的枚举…

enum Animal { Dog, Cat } 

…我基本上做了什么是定义了一个值types称为Animal与两个定义值, DogCat 。 这种types派生自引用types System.Enum (通常情况下,值types通常不会这样做 – 至less在C#中是不允许的,但在这种情况下允许这样做),并且具有用于来回转换int值的工具。

如果我刚刚描述的枚举types的方式是真实的,那么我期望下面的代码抛出一个InvalidCastException

 public class Program { public static void Main(string[] args) { // Box it. object animal = Animal.Dog; // Unbox it. How are these both successful? int i = (int)animal; Enum e = (Enum)animal; // Prints "0". Console.WriteLine(i); // Prints "Dog". Console.WriteLine(e); } } 

通常情况下, 您不能将System.Object的值types取消为除确切types之外的其他值 。 那么上述可能如何呢? 这就好像Animaltypes一个int (不只是可转换int ),同时也是一个Enum (不仅可以转换Enum )。 是多重inheritance吗? System.Enum以某种方式inheritanceSystem.Int32 (我不希望有可能)?

编辑 :它不能是以上任一。 下面的代码最终certificate了这一点(我认为):

 object animal = Animal.Dog; Console.WriteLine(animal is Enum); Console.WriteLine(animal is int); 

以上输出:

 真正
假 

枚举的MSDN文档和C#规范都使用术语“基础types”; 但是我不知道这是什么意思,也没有听说过它是用来引用除枚举之外的任何东西。 “底层types”究竟什么意思


那么,这又是一个从CLR获得特殊待遇的案例吗?

我的钱就是这样…但答案/解释会很好。


更新 : Damien_The_Unbeliever提供了真正回答这个问题的参考。 有关说明可以在CLI规范的分区II中的enums部分find:

为了约束的目的(例如,从用于调用它的方法引用来定位方法定义),枚举应该与它们的基础types不同。 出于所有其他目的,包括validation和执行代码, 一个unboxed枚举自由地与其基础types相互转换 。 枚举可以被装箱到相应的盒装实例types,但是这种types与底层types的盒装types不同,所以装箱不会丢失枚举的原始types。

编辑(再次?!) :等等,实际上,我不知道我第一次阅读正确。 也许它不是100%解释专门的拆箱行为本身(虽然我离开达米安的答案被接受,因为它在这个问题上大量的光)。 我会继续研究这个…


另一个编辑 :人,然后yodaj007的答案扔我另一个循环。 不知怎的,一个枚举不完全一样int ; 但一个int可以分配给一个枚举variables没有转换 ? 寮步?

我认为这一切都最终都是汉斯的回答 ,这就是为什么我接受了它。 (对不起,达米安!)

是的,特殊待遇 JIT编译器非常了解盒装值types的工作方式。 一般来说,价值types有点分裂。 拳击包括创build一个System.Object值,其行为与引用types的值完全相同。 此时,值types值不再像运行时的​​值一样。 这使得例如有一个像ToString()这样的虚拟方法成为可能。 盒装对象有一个方法表指针,就像引用types一样。

JIT编译器知道像int和bool这样的值types的方法表指针。 拳击和拆箱对他们来说是非常有效的,它只需要less量的机器代码指令。 这需要在.NET 1.0中高效地使其具有竞争力。 其中一个非常重要的部分是限制,值types值只能被拆箱到相同的types。 这样可以避免抖动产生一个调用正确转换代码的大量switch语句。 它所要做的就是检查对象中的方法表指针,并确认它是预期的types。 并直接复制对象的值。 值得注意的可能是这个限制在VB.NET中不存在,它的CType()运算符实际上是生成代码给包含这个大开关语句的帮助函数。

枚举types的问题是,这不能工作。 枚举可以有一个不同的GetUnderlyingType()types。 换句话说,unboxed的值有不同的大小,所以简单地把值从盒装对象中复制出来是行不通的。 敏锐地意识到,抖动不再内联取消装箱代码,它会产生一个调用CLR中的帮助函数。

该帮助程序名为JIT_Unbox(),您可以在SSCLI20源代码clr / src / vm / jithelpers.cpp中find其源代码。 你会看到它专门处理枚举types。 它是宽容的,它允许从一个枚举types拆箱到另一个。 但是只有底层types相同,如果不是这样的话,你会得到一个InvalidCastException。

这也是Enum被宣布为一个类的原因。 它的逻辑行为是一个引用types,派生的枚举types可以从一个到另一个。 有了上述对底层types兼容性的限制。 然而,枚举types的值具有非常多的值types值的行为。 他们有复制语义和装箱行为。

枚举由CLR特别处理。 如果你想进入血淋淋的细节,你可以下载MS分区II规范。 其中,你会发现枚举:

除了其他值types之外,枚举符合其他限制。 枚举应只包含字段作为成员(他们甚至不应该定义types初始值设定项或实例构造函数); 他们不应该实现任何接口; 他们应该有自动场布局(§10.1.2); 它们应该只有一个实例字段,并且应该是枚举的基本types; 所有其他领域应是静态的和字面的(§16.1);

所以这就是他们如何从System.Enuminheritance,但有一个“基础”types – 这是他们被允许具有的单个实例字段。

还有关于拳击行为的讨论,但是它没有明确地描述拆箱到底层的types,我可以看到。

分区I,8.5.2指出,枚举是“现有types的替代名称”,但是“或者匹配签名的目的,枚举不应与基础types相同”。

分区II,14.3阐述:“对于所有其他目的,包括validation和执行代码,一个unboxed枚举可以自由地与其基础types相互转换。枚举可以被装箱到相应的盒装实例types,但是这种types与盒装底层types的types,所以拳击不会丢失枚举的原始types。“

分区III,4.32解释了拆箱行为:“ obj中包含的值types的types必须赋值与valuetype兼容[注意:这会影响枚举types的行为,参见分区II.14.3。

我在这里注意的是从ECMA-335第38页(我build议你下载它只是为了它):

CTS支持枚举(也称为枚举types),即现有types的替代名称。 为了匹配签名的目的,枚举不应该与基础types相同。 但是,枚举的实例应该是可分配的 – 对于基础types,反之亦然。 也就是说,不需要强制转换(请参阅第8.3.3节)或强制转换(请参阅第8.3.2节)将枚举types转换为基础types,也不需要从基础types转换为枚举types。 一个枚举比一个真正的types有更多的限制,如下所示:

基础types应该是一个内置的整数types。 枚举应从System.Enum派生,因此它们是值types。 像所有的值types一样,它们应该被密封(见§8.9.9)。

 enum Foo { Bar = 1 } Foo x = Foo.Bar; 

由于第二句话,这个陈述将是错误的:

 x is int 

他们相同的(别名),但他们的签名是不一样的。 转换成int并不是一个强制转换。

从第46页开始:

基础types – 在CTS枚举中是现有types的替代名称(第8.5.2节),称为基础types。 除了签名匹配(第8.5.2节),枚举被视为其基础types。 这个子集是删除了枚举的存储types集合。

早些回到我的Foo enum。 这个声明将起作用:

 Foo x = (Foo)5; 

如果您检查reflection器中的Main方法生成的IL代码:

 .method private hidebysig static void Main(string[] args) cil managed { .entrypoint .maxstack 1 .locals init ( [0] valuetype ConsoleTesting.Foo x) L_0000: nop L_0001: ldc.i4.5 L_0002: stloc.0 L_0003: call string [mscorlib]System.Console::ReadLine() L_0008: pop L_0009: ret } 

请注意,没有演员。 ldc可以在第86页find。它加载一个常量。 在页151findi4 ,指示types是一个32位整数。 没有演员!

从MSDN提取:

枚举元素的默认底层types是int。 默认情况下,第一个枚举数的值为0,每个连续的枚举数的值增加1。

所以,演员是可能的,但你需要强制:

基础types指定为每个枚举器分配多less存储空间。 但是,需要显式强制转换才能从枚举types转换为整型。

当你将enum放入object ,动物对象是从System.Enum派生的(实际types在运行时是已知的),所以它实际上是一个int ,所以这个转换是有效的。

  • (animal is Enum)返回true :由于这个原因,你可以解除动物进入一个inttypes的Enum或事件,做一个明确的铸造。
  • (animal is int)返回falseis运算符(一般types检查)不检查枚举的基础types。 此外,由于这个原因,你需要做一个明确的转换来将Enum转换为int。

虽然枚举types是从System.Enuminheritance的,但它们之间的任何转换都不是直接的,而是装箱/拆箱。 来自C#3.0规范:

枚举types是具有命名常量的独特types。 每个枚举types都有一个基础types,它必须是字节,sbyte,short,ushort,int,uint,long或ulong。 枚举types的值的集合与基础types的值的集合相同。 枚举types的值不限于指定常量的值。 枚举types是通过枚举声明定义的

所以,虽然你的Animal类是从System.Enum派生的,但它实际上是一个int 。 顺便说一句,另一个奇怪的是System.Enum派生自System.ValueType ,但它仍然是一个引用types。

Enum的基础types是用于存储常量值的types。 在您的示例中,即使您没有明确定义值,C#也会这样做:

 enum Animal : int { Dog = 0, Cat = 1 } 

在内部, Animal由两个常量组成,整数值为0和1.这就是为什么您可以明确地将一个整数转换为一个Animal和一个Animal整数。 如果您将Animal.Dog传递给接受Animal的参数,那么您实际上正在传递Animal.Dog的32位整数值(在本例中为0)。 如果您给Animal一个新的基础types,那么这些值将被存储为该types。

为什么不呢……这是完全有效的,例如,一个结构内部保存一个int,并可以通过一个明确的转换运算符转换为int …让我们模拟一个枚举:

 interface IEnum { } struct MyEnumS : IEnum { private int inner; public static explicit operator int(MyEnumS val) { return val.inner; } public static explicit operator MyEnumS(int val) { MyEnumS result; result.inner = val; return result; } public static readonly MyEnumS EnumItem1 = (MyEnumS)0; public static readonly MyEnumS EnumItem2 = (MyEnumS)2; public static readonly MyEnumS EnumItem3 = (MyEnumS)10; public override string ToString() { return inner == 0 ? "EnumItem1" : inner == 2 ? "EnumItem2" : inner == 10 ? "EnumItem3" : inner.ToString(); } } 

这个结构可以用结构相同的方式…当然,如果你试图反映types,并调用IsEnum属性,它将返回false。

让我们来看看一些使用比较,与等价的枚举:

 enum MyEnum { EnumItem1 = 0, EnumItem2 = 2, EnumItem3 = 10, } 

比较用途:

结构版本:

 var val = MyEnum.EnumItem1; val = (MyEnum)50; val = 0; object obj = val; bool isE = obj is MyEnum; Enum en = val; 

枚举版本:

 var valS = MyEnumS.EnumItem1; valS = (MyEnumS)50; //valS = 0; // cannot simulate this object objS = valS; bool isS = objS is MyEnumS; IEnum enS = valS; 

有些操作是不能模拟的,但是这一切都显示了我想说的…枚举是特殊的,是的…有多特别? 没有那么多! =)