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!)