2011-01-07 16 views
40

Editar: Comentarios en la parte inferior. Además, this.¿Cómo es que una enumeración deriva de System.Enum y es un número entero al mismo tiempo?


Esto es lo que me confunde. Mi opinión es que si tengo una enumeración como esto ...

enum Animal 
{ 
    Dog, 
    Cat 
} 

... lo que en esencia lo que he hecho está definido un valor de tipo llamada Animal con dos valores definidos, Dog y Cat. Este tipo deriva del tipo de referencia System.Enum (algo que los tipos de valores normalmente no pueden hacer, al menos no en C#, pero que está permitido en este caso), y tiene una función para enviar y recibir valores de int .

Si el camino que acabo de describir el tipo de enumeración anterior fuera cierto, entonces yo esperaría que el siguiente código para lanzar un 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); 
    } 
} 

Normalmente, you cannot unbox a value type from System.Object as anything other than its exact type. Entonces, ¿cómo es posible lo anterior? Es como si el tipo Animalse un int (no sólo convertibles a int) y es unEnum (no sólo convertibles a Enum) al mismo tiempo. ¿Es herencia múltiple? ¿ System.Enum de alguna manera hereda de System.Int32 (algo que no esperaba que fuera posible)?

Editar: No puede ser cualquiera de los anteriores. El código siguiente muestra esto (creo) de manera concluyente:

object animal = Animal.Dog; 

Console.WriteLine(animal is Enum); 
Console.WriteLine(animal is int); 

Las salidas anteriores:

True 
False

Tanto the MSDN documentation on enumerations y la especificación de C# hacer uso de la expresión "tipo subyacente"; pero no sé lo que esto significa, ni lo he escuchado usar en referencia a cualquier cosa que no sea enumeraciones. ¿Qué significa "tipo subyacente" en realidad que significa?


Entonces, ¿es este otro case that gets special treatment from the CLR?

Mi dinero es en ese caso ... pero una respuesta/explicación sería agradable.


actualización: Damien_The_Unbeliever proporcionan la referencia a responder realmente a esta pregunta. La explicación se puede encontrar en la Partición II de la especificación CLI, en la sección en enumeraciones:

Para propósitos de vinculación (p., para localizando una definición de método a partir de la referencia de método utilizada para llamarlo) enumeraciones debe ser distinta de su tipo subyacente . Para todos los demás propósitos de , incluida la verificación y la ejecución del código, una enumeración no incluida interconvierte libremente con su tipo subyacente. Enumeraciones pueden ser encajonados correspondiente a un tipo de instancia en caja , pero este tipo es no la misma como el tipo de caja de la tipo subyacente, por lo que el boxeo no pierde la tipo original de la enumeración.

Editar (de nuevo ?!): Espera, en realidad, no sé que leí que bien la primera vez. Quizás no explique al 100% el comportamiento de unboxing especializado en sí mismo (aunque estoy dejando la respuesta de Damien como aceptada, ya que arrojó mucha luz sobre este tema). Voy a seguir buscando en este ...


Otra Editar: Hombre, entonces yodaj007's answer me tiró para otro bucle. De alguna manera, una enumeración no es exactamente lo mismo que un int; sin embargo, se puede asignar un int a una variable enum sin conversión? Buh?

Creo que esto finalmente se ilumina con Hans's answer, y es por eso que lo acepté.

Respuesta

22

Sí, un tratamiento especial. El compilador JIT es muy consciente de la forma en que funcionan los tipos de valores en caja. Que es en general lo que hace que los tipos de valores actúen un poco esquizoides. El boxeo implica la creación de un valor System.Object que se comporta exactamente de la misma manera que un valor de un tipo de referencia. En ese punto, los valores de tipo de valor ya no se comportan como lo hacen los valores en el tiempo de ejecución. Lo que hace posible, por ejemplo, tener un método virtual como ToString(). El objeto en caja tiene un puntero de tabla de método, al igual que los tipos de referencia.

El compilador JIT conoce los punteros de las tablas de métodos para tipos de valores como int y bool front. Boxeo y unboxing para ellos es muy eficiente, solo se necesita un puñado de instrucciones de código de máquina. Esto necesitaba ser eficiente en .NET 1.0 para hacerlo competitivo. Un muy muy importante es la restricción de que un valor de tipo de valor solo puede ser desempaquetado al mismo tipo. Esto evita que el jitter tenga que generar una instrucción de conmutación masiva que invoca el código de conversión correcto. Todo lo que tiene que hacer es verificar el puntero de la tabla de métodos en el objeto y verificar que sea el tipo esperado. Y copie el valor directamente del objeto. Notable quizás es que esta restricción no existe en VB.NET, su operador CType() de hecho genera código a una función auxiliar que contiene esta gran declaración de cambio.

El problema con los tipos Enum es que esto no puede funcionar. Los mensajes pueden tener un tipo GetUnderlyingType() diferente. En otras palabras, el valor unboxed tiene diferentes tamaños, por lo que simplemente copiar el valor del objeto en caja no puede funcionar.Consciente de que el jitter ya no enmarca el código de unboxing, genera una llamada a una función auxiliar en el CLR.

Esa ayuda se llama JIT_Unbox(), puede encontrar su código fuente en la fuente SSCLI20, clr/src/vm/jithelpers.cpp. Lo verá especialmente relacionado con los tipos enum. Es permisivo, permite unboxing de un tipo de enumeración a otro. Pero solo si el tipo subyacente es el mismo, se obtiene una InvalidCastException si ese no es el caso.

Cuál es también la razón por la que Enum se declara como clase. Su comportamiento lógico es de un tipo de referencia, los tipos de enumeración derivados se pueden convertir de uno a otro. Con la restricción mencionada anteriormente sobre la compatibilidad de tipo subyacente. Sin embargo, los valores de un tipo de enumeración tienen mucho el comportamiento de un valor de tipo de valor. Tienen semántica de copia y comportamiento de boxeo.

+4

Información impresionante. Siento que aprendo 25 cosas nuevas cada vez que leo una de tus respuestas ... –

+0

Hans Passant, pero ** ¿por qué eligieron "permitir que las enumeraciones y su tipo primitivo sean intercambiables" como dice el comentario en el código C++? Consulte mi nueva pregunta sobre el desempaquetado combinado y la conversión enum (mi pregunta está vinculada a esta pregunta anterior). –

+0

El problema es que usted supone que la conversión de unboxing C# se comporta de la manera normal. Es lo opuesto, es la manera excepcional. Compruebe esta respuesta por la razón por la cual es excepcional: http://stackoverflow.com/questions/1583050/performance-surprise-with-as-and-nullable-types/3076525#3076525 –

3

Extraído de MSDN (Lo sentimos, Damien!):

El valor predeterminado tipo subyacente de los elementos de enumeración es int. Por defecto, la primera empadronador tiene el valor 0, y el valor de cada empadronador sucesiva se incrementa en 1.

Por lo tanto, el elenco es posible, pero hay que forzarlo:

El El tipo subyacente especifica la cantidad de almacenamiento asignada para cada enumerador. Sin embargo, se necesita un molde explícito para convertir del tipo enum a un tipo integral.

Cuando Cuadro de su enumeración en object, el objeto se deriva de animales System.Enum (el tipo real es conocido en tiempo de ejecución) por lo que es en realidad un int, por lo que el reparto es válido.

  • (animal is Enum) vuelve true: Por esta razón se puede unbox animal en una enumeración o evento en un int haciendo una conversión explícita.
  • (animal is int) devuelve false: El operador is (en verificación de tipo general) no comprueba el tipo subyacente de Enumerados. Además, por esta razón, debe hacer una conversión explícita para convertir Enum a int.
+0

OK, pero si compruebo 'if (animal es Enum)' obtengo 'true', mientras que si compruebo' if (animal es int) 'obtengo' false'. Esto es lo que me confunde (junto con el hecho de que puedo unbox de 'object' a' int'). Tal vez debería agregar esto a la pregunta ... –

+0

Es posible un lanzamiento explícito de 'long' a' int', pero [no se puede desagrupar un recuadro 'long' como' int'] (http: //philosopherdeveloper.wordpress .com/2010/05/05/the-difference-between-converting-and-unboxing-read-my-mind-compiler /). Este era mi punto; en realidad * unboxing * y 'Enum' como' int' es legal, lo que no puede explicarse simplemente por un molde explícito.Creo que tu respuesta es correcta, pero siento que estás extrañando lo que en realidad estaba preguntando. (Vea mi actualización en respuesta a la respuesta de Damien_The_Unbeliever para ver a qué me refiero.) –

+0

Ok, veo su punto. Estoy jugando con reflector y 'Enum.Parse'. Esta llamada devuelve un objeto, y tal vez su pregunta es la misma. Al final de la llamada, este método llama a 'InternalBoxEnum (Type enumType, long value);' Pero, tiene razón, no le di la respuesta real, pero parece ser otro caso especial de CRL. –

2

Mientras que los tipos enum se heredan de System.Enum, cualquier conversión entre ellos no es directa, sino de boxeo/desempaquetado. Desde C# 3.0 Especificación:

Un tipo de enumeración es un tipo distinto con constantes con nombre. Cada tipo de enumeración tiene un tipo subyacente, que debe ser byte, sbyte, corto, ushort, int, uint, largo o ulong. El conjunto de valores del tipo de enumeración es el mismo que el conjunto de valores del tipo subyacente. Los valores del tipo de enumeración no son restringidos a los valores de las constantes nombradas. tipos de enumeración se definen a través de la enumeración declaraciones

Así, mientras que la clase de animal se deriva de System.Enum, en realidad es un int. Por cierto, otra cosa extraña es System.Enum se deriva de System.ValueType, sin embargo, sigue siendo un tipo de referencia.

+1

"Derivado de' System.Enum', pero * is * an 'int'": ¡esto es lo que no estoy entendiendo! ¿Puede explicar más sobre * qué significa esto realmente *? (En particular, no creo tener una noción clara de lo que significa "tipo subyacente". Claramente, no es lo mismo que "tipo de base"). –

+0

Esto es una suposición: probablemente sea como la dualidad de int y System. Int32. Al almacenar y hacer aritmética básica, actúa como un valor y, cuando es necesario, actúa como un tipo de referencia. – sukru

+0

No creo que sea así de simple, basado en el hecho de que 'animal is int' se evalúa como' false' (ver mi edición). –

8

Enumerados especialmente por el CLR. Si desea entrar en detalles sangrientos, puede descargar la especificación MS Partition II. En él, encontrará que Enums:

Enumeraciones obedecen restricciones adicionales más allá de aquellos en otros tipos de valor. Los enumerados contendrán solo campos como miembros (ni siquiera definirán los inicializadores de tipo o los constructores de instancia ); no deberán implementar ninguna interfaz; ellos tendrán diseño de campo automático (§10.1.2); tendrán exactamente un campo de instancia y será del tipo subyacente de la enumeración; todos los demás campos serán estáticos y literales (§16.1);

Así es como pueden heredar de System.Enum, pero tienen un tipo "subyacente": es el único campo de instancia que pueden tener.

También hay una discusión sobre el comportamiento del boxeo, pero no describe unboxing explícitamente al tipo subyacente, que puedo ver.

+0

Siento que definitivamente nos estamos acercando con esta respuesta; Gracias. Estoy echando un vistazo a esa especificación ahora; ¿Sabes por casualidad si incluye una asignación especial para desempaquetar una enumeración en ese campo de instancia única que mencionaste? –

+0

Esto fue totalmente clave para mí. Incluso encontré la parte donde define explícitamente el comportamiento de unboxing, que agregaré como una actualización. ¡Gracias de nuevo! –

+0

Por favor, observe mi respuesta. – Amy

1

Un tipo subyacente de Enum es el tipo utilizado para almacenar el valor de las constantes. En su ejemplo, a pesar de que no se ha definido de forma explícita los valores, C# hace esto:

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

Internamente, Animal se compone de dos constantes con el número entero valores 0 y 1. Por eso se puede convertir explícitamente una entero a Animal y Animal a un número entero. Si pasa Animal.Dog a un parámetro que acepta un Animal, lo que realmente está haciendo es pasar el valor entero de 32 bits de Animal.Dog (en este caso, 0). Si otorga Animal un nuevo tipo subyacente, los valores se almacenan como ese tipo.

4

Lo que estoy señalando aquí es de la página 38 de ECMA-335 (le sugiero que descargar simplemente para tenerlo):

El CTS es compatible con una enumeración (también conocido como un tipo de enumeración), un suplente nombre para un tipo existente. A los efectos de la coincidencia de firmas, una enumeración no será la misma que el tipo subyacente.Sin embargo, las instancias de una enumeración se pueden asignar al tipo subyacente y viceversa. Es decir, no se requiere conversión (ver §8.3.3) o coerción (ver §8.3.2) para convertir de la enumeración al tipo subyacente, ni se requieren desde el tipo subyacente a la enumeración. Una enumeración es considerablemente más restringida que un tipo verdadero, de la siguiente manera:

El tipo subyacente debe ser un tipo entero integrado. Los enumeraciones se derivarán de System.Enum, por lo tanto, son tipos de valores. Como todos los tipos de valores, deben estar sellados (ver §8.9.9).

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

Esta declaración es falsa debido a la segunda frase:

x is int 

Ellos son la misma (un alias), pero su firma no es la misma. La conversión hacia y desde un int no es un elenco.

De la página 46:

tipos subyacentes - en las enumeraciones de CTS son nombres alternativos para los tipos existentes (§8.5.2), denominado su tipo subyacente. Excepto por la coincidencia de firmas (§8.5.2), las enumeraciones se tratan como su tipo subyacente. Este subconjunto es el conjunto de tipos de almacenamiento con las enumeraciones eliminadas.

Regrese a mi página anterior. Esta declaración funcionará:

Foo x = (Foo)5; 

Si inspecciona el código IL generada de mi método principal de reflector:

.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 
} 

Nota no hay elenco. ldc se encuentra en la página 86. Carga una constante. i4 se encuentra en la página 151, lo que indica que el tipo es un entero de 32 bits. No hay un elenco!

+0

Gahhh ... ahora solo estás confundiendo mi pobre mente. Parece que la especificación CLI de alguna manera define las enumeraciones como * no * siendo lo mismo que su "tipo subyacente" (y derivado de 'System.Enum') mientras que de alguna manera también * es el mismo * al mismo tiempo. Ya no sé qué creer ... –

+0

@Dan, es por eso que estoy aquí. Ahora que mi trabajo está hecho, es hora de almorzar. – Amy

4

La Partición I, 8.5.2 establece que las enumeraciones son "un nombre alternativo para un tipo existente" pero "[f] o para fines de concordancia de firmas, una enumeración no será la misma que el tipo subyacente". Partition II, 14.3 explica: "Para todos los demás fines, incluida la verificación y ejecución de código, una enumeración unboxed interconvierte libremente con su tipo subyacente Los enumerados pueden encajonarse en un tipo de instancia encuadrado correspondiente, pero este tipo no lo mismo que el tipo en caja del tipo subyacente, por lo que el boxeo no pierde el tipo original de la enumeración ".

partición III, 4,32 explica el comportamiento unboxing:. "El tipo de valor de tipo de contenida dentro obj debe ser asignación compatible con valuetype [Nota: Esto afecta el comportamiento con los tipos enum, ver partición II. 14.3. Nota final] "

1

Por qué no ... es perfectamente válido, por ejemplo, para una estructura para mantener internamente y ser convertible a int con un operador de conversión explícito ... simulemos un Enum:

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(); 
    } 
} 

Esta estructura se puede usar de la misma manera que una estructura ... por supuesto, si intentas reflejar el tipo y llamar a la propiedad IsEnum, devolverá falso. mirada

Vamos a alguna comparación uso, con la enumeración equivalente:

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

usos Comparación: Versión

Struct:

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

Enum:

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

Algunas operaciones no se pueden simular, pero todo esto muestra lo que pretendía decir ... Los mensajes son especiales, sí ... ¿cuánto especial? ¡no mucho! =)

Cuestiones relacionadas