que enum .net inheritance enums clr unboxing

.net - que - enum c#



¿Cómo es que una enumeración deriva de System.Enum y es un número entero al mismo tiempo? (8)

¿Por qué no ... es perfectamente válido, por ejemplo, para que una estructura contenga un int internamente y sea 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 llama a la propiedad IsEnum, devolverá falsa.

Veamos alguna comparación de uso, con la enumeración equivalente:

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

Comparando usos:

Versión de Struct:

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

Versión de 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! =)

Editar : Comentarios en la parte inferior. Además, this .

Esto es lo que me confunde. Mi entendimiento es que si tengo una enumeración como esta ...

enum Animal { Dog, Cat }

... lo que esencialmente he hecho es definir un tipo de valor llamado 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 transmitir de ida y vuelta a / from int values .

Si la forma en que acabo de describir el tipo de enumeración anterior fuera verdadera, entonces esperaría que el siguiente código arrojara una 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, no puede desvincular un tipo de valor de System.Object como otro que no sea su tipo exacto . Entonces, ¿cómo es posible lo anterior? Es como si el tipo Animal un int (no solo convertible a int ) y es un Enum (no solo convertible a Enum ) al mismo tiempo. ¿Es herencia múltiple? ¿ System.Enum alguna manera hereda de System.Int32 (algo que no esperaba que fuera posible)?

Editar : No puede ser cualquiera de los anteriores. El siguiente código demuestra esto (creo) concluyentemente:

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

Los resultados anteriores:

True False

Tanto la documentación de MSDN sobre las enumeraciones como la especificación C # hacen uso del término "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 realmente "tipo subyacente"?

Entonces, ¿es este otro caso más que recibe un trato especial del CLR ?

Mi dinero está en que ese sea el caso ... pero una respuesta / explicación sería agradable.

Actualización : Damien_The_Unbeliever proporcionó la referencia para responder verdaderamente a esta pregunta. La explicación se puede encontrar en la Partición II de la especificación CLI, en la sección sobre enumeraciones:

Para fines vinculantes (por ejemplo, para ubicar una definición de método a partir del método de referencia utilizado para llamarla) las enumeraciones deben ser distintas de su tipo subyacente. Para todos los demás fines, incluida la verificación y la ejecución del código, una enumeración unboxed se interconvierte libremente con su tipo subyacente . Los mensajes pueden encajonarse en un tipo de instancia encuadrada correspondiente, pero este tipo no es lo mismo que el tipo encuadrado del tipo subyacente, por lo que el boxeo no pierde el tipo original de la enumeración.

Editar (¿de nuevo ?!) : Espera, en realidad, no sé si lo leí 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). Continuaré investigando esto ...

Otro Editar : Hombre, luego la respuesta de yodaj007 me lanzó a otro ciclo. De alguna manera, una enumeración no es exactamente lo mismo que una int ; sin embargo, se puede asignar un int a una variable enum sin conversión ? Buh?

Creo que todo esto finalmente está iluminado por la respuesta de Hans , y es por eso que lo acepté. (Lo siento, Damien!)


El tipo subyacente de Enum es el tipo utilizado para almacenar el valor de las constantes. En su ejemplo, aunque no haya definido explícitamente los valores, C # hace esto:

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

Internamente, Animal se compone de dos constantes con los valores enteros 0 y 1. Es por eso que puedes convertir explícitamente un entero a un Animal y un Animal a un número entero. Si pasas Animal.Dog a un parámetro que acepta un Animal , lo que realmente estás haciendo es pasar el valor entero de 32 bits de Animal.Dog (en este caso, 0). Si le da a Animal un nuevo tipo subyacente, entonces los valores se almacenan como ese tipo.


Extraído de MSDN :

El tipo subyacente predeterminado de los elementos de enumeración es int. Por defecto, el primer enumerador tiene el valor 0, y el valor de cada enumerador sucesivo se incrementa en 1.

Entonces, el elenco es posible, pero debes forzarlo:

El tipo subyacente especifica cuánto espacio de almacenamiento se asigna para cada enumerador. Sin embargo, se necesita un molde explícito para convertir del tipo enum a un tipo integral.

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

  • (animal is Enum) devuelve true : por esta razón puedes unbox animal en un Enum o evento en un int haciendo un casting explícito.
  • (animal is int) devuelve false : El operador is (en general, verificación de tipo) no verifica el tipo subyacente de Enums. Además, por esta razón, debe hacer una conversión explícita para convertir Enum a int.

La Partición I, 8.5.2 establece que las enumeraciones son "un nombre alternativo para un tipo existente", pero "[f] o los fines de la coincidencia de firmas, una enumeración no será la misma que el tipo subyacente".

Partition II, 14.3 expone: "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 es el mismo que el encasillado tipo del tipo subyacente, por lo que el boxeo no pierde el tipo original de la enumeración ".

La Partición III, 4.32 explica el comportamiento de unboxing: "El tipo de tipo de valor contenido dentro de obj debe ser compatible con la asignación valuetype . [Nota: Esto afecta el comportamiento con los tipos enum, ver Partition II.14.3. End note]"


Lo que estoy viendo aquí es de la página 38 de ECMA-335 (sugiero que lo descarguen solo para tenerlo):

El CTS admite una enumeración (también conocida como tipo de enumeración), un nombre alternativo 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 de 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 será falsa debido a la segunda oración:

x is int

Son lo mismo (un alias), pero su firma no es la misma. La conversión a y desde un int no es un elenco.

Desde la página 46:

tipos subyacentes: en las enumeraciones CTS son nombres alternativos para los tipos existentes (§8.5.2), denominados 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.

Regresa a mi Foo enum antes. Esta declaración funcionará:

Foo x = (Foo)5;

Si inspecciona el código IL generado de mi método Principal en 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 }

Tenga en cuenta que no hay yeso. 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!


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

Los mensajes obedecen a restricciones adicionales más allá de los de otros tipos de valores. Los enumerados contendrán solo campos como miembros (ni siquiera definirán los inicializadores de tipo o los constructores de instancias); no implementarán ninguna interfaz; deberán tener un 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ícito para el tipo subyacente, que puedo ver.


Mientras que los tipos de enumeración se heredan de System.Enum , cualquier conversión entre ellos no es directa, sino de boxeo / desembalaje. De la especificación C # 3.0:

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, short, ushort, int, uint, long 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 están restringidos a los valores de las constantes nombradas. Los tipos de enumeración se definen a través de declaraciones de enumeración

Entonces, aunque su clase Animal se deriva de System.Enum , en realidad es una int . Por cierto, otra cosa extraña es System.Enum se deriva de System.ValueType , sin embargo, sigue siendo un tipo de referencia.


Sí, trato 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. El boxeo y el desempaquetado de ellos es muy eficiente, solo se necesitan unas pocas instrucciones de código de máquina. Esto necesitaba ser eficiente en .NET 1.0 para hacerlo competitivo. Una parte muy importante de eso 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 de 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.

Que es también la razón por la cual Enum se declara como una clase. Su comportamiento lógico es de tipo de referencia, los tipos de enum 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.