Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How is it that an enum derives from System.Enum and is an integer at the same time?

Edit: Comments at bottom. Also, this.


Here's what's kind of confusing me. My understanding is that if I have an enum like this...

enum Animal {     Dog,     Cat } 

...what I've essentially done is defined a value type called Animal with two defined values, Dog and Cat. This type derives from the reference type System.Enum (something which value types can't normally do—at least not in C#—but which is permitted in this case), and has a facility for casting back and forth to/from int values.

If the way I just described the enum type above were true, then I would expect the following code to throw an 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);     } } 

Normally, you cannot unbox a value type from System.Object as anything other than its exact type. So how is the above possible? It is as if the Animal type is an int (not just convertible to int) and is an Enum (not just convertible to Enum) at the same time. Is it multiple inheritance? Does System.Enum somehow inherit from System.Int32 (something I would not have expected to be possible)?

Edit: It can't be either of the above. The following code demonstrates this (I think) conclusively:

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

The above outputs:

True False

Both the MSDN documentation on enumerations and the C# specification make use of the term "underlying type"; but I don't know what this means, nor have I ever heard it used in reference to anything other than enums. What does "underlying type" actually mean?


So, is this yet another case that gets special treatment from the CLR?

My money's on that being the case... but an answer/explanation would be nice.


Update: Damien_The_Unbeliever provided the reference to truly answer this question. The explanation can be found in Partition II of the CLI specification, in the section on enums:

For binding purposes (e.g., for locating a method definition from the method reference used to call it) enums shall be distinct from their underlying type. For all other purposes, including verification and execution of code, an unboxed enum freely interconverts with its underlying type. Enums can be boxed to a corresponding boxed instance type, but this type is not the same as the boxed type of the underlying type, so boxing does not lose the original type of the enum.

Edit (again?!): Wait, actually, I don't know that I read that right the first time. Maybe it doesn't 100% explain the specialized unboxing behavior itself (though I'm leaving Damien's answer as accepted, as it shed a great deal of light on this issue). I will continue looking into this...


Another Edit: Man, then yodaj007's answer threw me for another loop. Somehow an enum is not exactly the same as an int; yet an int can be assigned to an enum variable with no cast? Buh?

I think this is all ultimately illuminated by Hans's answer, which is why I've accepted it. (Sorry, Damien!)

like image 203
Dan Tao Avatar asked Jan 07 '11 14:01

Dan Tao


People also ask

Is enum an integer?

Internally, each enum value contains an integer, corresponding to the order in which they are declared in the source code, starting from 0.

Can enum be derived?

Enums cannot inherit from other enums. In fact all enums must actually inherit from System.

Can enum inherit enum?

As we have learned, we cannot inherit enum classes in Java. However, enum classes can implement interfaces.


1 Answers

Yes, special treatment. The JIT compiler is keenly aware of the way boxed value types work. Which is in general what makes value types acting a bit schizoid. Boxing involves creating a System.Object value that behaves exactly the same way as a value of a reference type. At that point, value type values no longer behave like values do at runtime. Which makes it possible, for example, to have a virtual method like ToString(). The boxed object has a method table pointer, just like reference types do.

The JIT compiler knows the method tables pointers for value types like int and bool up front. Boxing and unboxing for them is very efficient, it takes but a handful of machine code instructions. This needed to be efficient back in .NET 1.0 to make it competitive. A very important part of that is the restriction that a value type value can only be unboxed to the same type. This avoids the jitter from having to generate a massive switch statement that invokes the correct conversion code. All it has to do is to check the method table pointer in the object and verify that it is the expected type. And copy the value out of the object directly. Notable perhaps is that this restriction doesn't exist in VB.NET, its CType() operator does in fact generate code to a helper function that contains this big switch statement.

The problem with Enum types is that this cannot work. Enums can have a different GetUnderlyingType() type. In other words, the unboxed value has different sizes so simply copying the value out of the boxed object cannot work. Keenly aware, the jitter doesn't inline the unboxing code anymore, it generates a call to a helper function in the CLR.

That helper is named JIT_Unbox(), you can find its source code in the SSCLI20 source, clr/src/vm/jithelpers.cpp. You'll see it dealing with enum types specially. It is permissive, it allows unboxing from one enum type to another. But only if the underlying type is the same, you get an InvalidCastException if that's not the case.

Which is also the reason that Enum is declared as a class. Its logical behavior is of a reference type, derived enum types can be cast from one to another. With the above noted restriction on the underlying type compatibility. The values of an enum type have however very much the behavior of a value type value. They have copy semantics and boxing behavior.

like image 192
Hans Passant Avatar answered Sep 20 '22 16:09

Hans Passant