枚举是如何从System.Enum派生出来的,同时又是一个整数?

Dan*_*Tao 41 .net clr inheritance enums unboxing

编辑:底部的评论.还有,这个.


这就是让我感到困惑的一点.我的理解是,如果我有这样的枚举......

enum Animal
{
    Dog,
    Cat
}
Run Code Online (Sandbox Code Playgroud)

...我基本上完成的是定义了一个用两个定义的值调用的值类型Animal,DogCat.这种类型派生自引用类型 System.Enum(值类型通常不能执行的操作 - 至少不在C#中 - 但在这种情况下是允许的),并且具有用于在int值之间来回转换的工具.

如果我刚刚描述上面的枚举类型的方式是真的,那么我希望以下代码抛出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);
    }
}
Run Code Online (Sandbox Code Playgroud)

通常,您不能将值类型System.Object从其确切类型以外的任何其他方式取消装箱.那以上怎么可能呢?这是因为如果该Animal类型一个int(不只是转换int),并一个Enum(不只是转换Enum在同一时间).它是多重继承吗?是否System.Enum从某种程度上继承System.Int32(东西我也没有想到是可能的)?

编辑:它不能是上述任何一个.以下代码最终证明了这一点(我认为):

object animal = Animal.Dog;

Console.WriteLine(animal is Enum);
Console.WriteLine(animal is int);
Run Code Online (Sandbox Code Playgroud)

以上输出:

True
False

两者上枚举MSDN文档和C#说明书使用的术语"基本类型"的; 但是我不知道这意味着什么,也没有听过它用于引用除枚举之外的任何东西."底层类型"究竟意味着什么?


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

我的钱是这样的......但答案/解释会很好.


更新:Damien_The_Unbeliever提供了真正回答这个问题的参考.可以在CLI规范的分区II中的枚举部分中找到解释:

出于绑定目的(例如,用于从用于调用它的方法引用中定位方法定义),枚举应与其基础类型不同.出于所有其他目的,包括验证和执行代码,未装箱的枚举与其基础类型自由地互换.枚举可以装箱到相应的包装实例类型,但该类型是一样的盒装类型的基础类型的,所以拳击不丢失原始类型的枚举.

编辑(再次?!):等等,实际上,我不知道我第一次读到这是正确的.也许它不能100%解释专门的拆箱行为本身(虽然我将Damien的答案视为已被接受,因为它在这个问题上有很多亮点).我会继续研究这个......


另一个编辑:男人,然后yodaj007的回答让我再次循环.某种程度上,枚举并不完全相同int; 又一个int可以被分配到一个枚举变量与不投?寮步?

我认为这最终都是汉斯的答案所阐明,这也是我接受它的原因.(对不起,达米恩!)

Han*_*ant 24

是的,特殊待遇.JIT编译器非常清楚盒装值类型的工作方式.这通常是什么使得价值类型表现得有点精神错乱.拳击涉及创建一个System.Object值,其行为与引用类型的值完全相同.此时,值类型值不再像运行时的​​值那样运行.例如,这使得有可能拥有像ToString()这样的虚方法.盒装对象有一个方法表指针,就像引用类型一样.

JIT编译器知道方法表指针的值类型,如int和bool.为他们装箱和拆箱非常有效,只需要少量的机器代码指令.这需要在.NET 1.0中高效,以使其具有竞争力.其中一个非常重要的部分是限制值类型值只能取消装箱到同一类型.这避免了抖动必须生成调用正确转换代码的大量switch语句.它所要做的就是检查对象中的方法表指针并验证它是否是预期的类型.并直接从对象中复制值.值得注意的是,这种限制在VB.NET中不存在,其CType()运算符确实生成了包含这个大switch语句的辅助函数的代码.

Enum类型的问题是这不起作用.枚举可以具有不同的GetUnderlyingType()类型.换句话说,未装箱的值具有不同的大小,因此简单地将值从盒装对象中复制不起作用.敏锐地意识到,抖动不再内联拆箱代码,它会在CLR中生成对辅助函数的调用.

该帮助程序名为JIT_Unbox(),您可以在SSCLI20源代码clr/src/vm/jithelpers.cpp中找到它的源代码.你会看到它专门处理枚举类型.这是允许的,它允许从一个枚举类型到另一个枚举类型的拆箱.但是,只有当底层类型相同时,如果不是这种情况,则会得到InvalidCastException.

这也是Enum被宣布为班级的原因.它的逻辑行为是引用类型,派生的枚举类型可以从一个转换为另一个.具有上述对底层类型兼容性的限制.然而,枚举类型的值具有值类型值的行为.他们有复制语义和拳击行为.

  • 很棒的信息.每当我读到你的一个答案时,我觉得我学习了25件新东西...... (5认同)

Dam*_*ver 9

枚举由CLR专门处理.如果您想了解血腥细节,可以下载MS Partition II规范.在其中,你会发现Enums:

Enums遵循除其他值类型之外的其他限制.枚举应仅包含字段作为成员(它们甚至不应定义类型初始化器或实例构造器); 他们不得实施任何接口; 他们应有自动场布局(§10.1.2); 它们应该只有一个实例字段,它应该是枚举的基础类型; 所有其他字段应为静态和字面(第16.1节);

这就是他们如何从System.Enum继承,但具有"底层"类型 - 它是允许的单实例字段.

还有一个关于拳击行为的讨论,但它没有明确地描述基础类型的拆箱,我可以看到.