Pregunta

Editar : Observaciones en el fondo. También, este .


Esto es lo que es una especie de confundirme. Mi opinión es que si tengo una enumeración como este ...

enum Animal
{
    Dog,
    Cat
}

... lo que he hecho esencialmente se define un tipo de valor llamada Animal con dos valores definidos, Dog y Cat. Este tipo se deriva de la tipo de referencia System.Enum (algo que los tipos de valor normalmente no pueden hacerlo, al menos no en C # -pero que está permitido en este caso), y cuenta con una instalación para la fundición de ida y vuelta a / de valores int.

Si la forma que acabo de describir el tipo de enumeración anterior fuera cierto, entonces yo esperaría que el código siguiente para lanzar 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 se puede unbox un tipo de valor de System.Object como otra cosa que no sea su tipo exacto . Entonces, ¿cómo es posible lo anterior? Es como si el tipo Animal es una int (no sólo convertible a int) y es una Enum (no sólo convertible a Enum) al mismo tiempo. ¿Es la herencia múltiple? No System.Enum hereda de alguna manera System.Int32 (algo que no habría esperado a ser 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

la documentación de MSDN en enumeraciones y el C # especificación hacer uso de la expresión "tipo subyacente"; pero no sé lo que esto significa, ni he oído que se utiliza en referencia a otra cosa que enumeraciones nada. ¿Qué significa "tipo subyacente" en realidad media


Por lo tanto, es este otro caso de que recibe un tratamiento especial del CLR ?

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


Actualizar Damien_The_Unbeliever proporciona la referencia a realmente responder a esta pregunta. La explicación puede encontrarse en la partición II de la especificación CLI, en la sección sobre enumeraciones:

  

Para los propósitos de unión (por ejemplo, para   la localización de una definición del método de la   Método de referencia utilizado para llamar)   enumeraciones serán distintos de su   tipo subyacente. Para todos los demás   propósitos, incluyendo la verificación y   ejecución de código, una enumeración sin embalaje   interconvierte libremente con su   tipo subyacente . Enumeraciones pueden ser encajonados   a una instancia de caja correspondiente   tipo, pero este tipo es no de la misma   como el tipo de caja de la subyacente   escribir, por lo que el boxeo no pierde la   tipo original de la enumeración.

Editar (de nuevo?!): Espere, en realidad, no sé que leí que bien la primera vez. Tal vez no lo hace 100% explicar el comportamiento unboxing especializado en sí (aunque estoy dejando la respuesta de Damien como aceptado, ya que derramó una gran cantidad de luz sobre esta cuestión). Voy a seguir buscando en este ...


Otra Editar : Hombre, entonces yodaj007 de respuesta tiró de mí para otro bucle de alguna manera una enumeración no es exactamente lo mismo que un int;.?? sin embargo, una int se puede asignar a una variable de enumeración sin molde Buh

creo que todo esto es en última instancia, iluminado por respuesta de Hans, por lo que lo he aceptado. (Lo siento, Damien!)

¿Fue útil?

Solución

Sí, el tratamiento especial. El compilador JIT es muy consciente de la manera en caja tipos de valores de trabajo. Que es, en general, lo que hace que los tipos de valores actuando un poco esquizoide. 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 tiempo de ejecución. Lo que lo hace posible, por ejemplo, tener un método virtual como ToString (). El objeto en caja tiene un puntero de tabla de métodos, al igual que lo hacen los tipos de referencia.

El compilador JIT conoce los punteros mesas método para tipos de valor como int y bool en la delantera. Boxeo y unboxing para ellos es muy eficiente, se necesita más que un puñado de instrucciones de código máquina. Esto tenía que ser eficiente en la espalda .NET 1.0 para que sea competitiva. Un muy parte importante de esto es la restricción de que un valor de tipo de valor sólo puede ser sin embalaje al mismo tipo. Esto evita la fluctuación de tener que generar una instrucción switch masiva que invoca el código de conversión correcta. Todo lo que tiene que hacer es comprobar el puntero de la tabla método en el objeto y verificar que es el tipo esperado. Y copiar el valor del objeto directamente. Notable quizás es que esta restricción no existe en VB.NET, su CType () operador no en el hecho de generar código para una función de ayuda que contiene esta instrucción switch grande.

El problema con los tipos de enumeración es que esto no puede funcionar. Enums pueden tener un tipo diferente GetUnderlyingType (). En otras palabras, el valor sin embalaje tiene diferentes tamaños por lo que simplemente copiando el valor del objeto en caja no puede trabajar. Con clara visión, la fluctuación no coloca en línea el código unboxing más, se genera una llamada a una función de ayuda en el CLR.

Eso es nombrado ayudante JIT_Unbox (), puede encontrar su código fuente en la fuente SSCLI20, clr / src / vm / jithelpers.cpp. Verás que se trata de tipos de enumeración especialmente. Es permisiva, que permite unboxing de un tipo de enumeración a otro. Pero sólo si el tipo subyacente es la misma, 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 una clase. Su lógico comportamiento es de un tipo de referencia, los tipos enum derivados pueden ser emitidos de una a otra. Con la restricción anteriormente mencionada sobre la compatibilidad tipo subyacente. Los valores de un tipo de enumeración tienen sin embargo en gran medida el comportamiento de un valor de tipo de valor. Ellos tienen una semántica de copia y el comportamiento de boxeo.

Otros consejos

Las enumeraciones están especialmente tratadas por el CLR. Si quieres entrar en los detalles morbosos, puede descargar el MS partición II especificación. En él, se encontrará que las enumeraciones:

  

enumeraciones Acatar las restricciones adicionales   más allá de las de otros tipos de valores.   Enumeraciones sólo contendrán los campos como   miembros (no deberán incluso definir   escriba inicializadores o instancia   constructores); No tendrán   implementar las interfaces; ellos   deberán tener disposición del campo de auto   (§10.1.2); que deberán tener exactamente una   campo de instancia y será del tipo subyacente de   la enumeración; todos los demás campos serán   estático y literal (§16.1);

Así que esa es la forma en que se pueden heredar de System.Enum, pero que tienen un tipo de "subyacente" -. Que es el campo de instancia única que se les permita tener

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

Partición I, 8.5.2 estados que enumeraciones son "un nombre alternativo para un tipo existente" pero "[F] o los fines de Firmas, una enumeración no deberá ser el mismo que el tipo subyacente."

Partición II, 14.3 expone: ". Para todos los otros propósitos, incluyendo la verificación y la ejecución de código, una enumeración unboxed libremente interconvierte con su tipo subyacente Enums pueden ser encajonados a un correspondiente tipo de instancia en caja, pero este tipo no es el mismo como el tipo de 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 tipo de valor contenido dentro de obj debe ser asignación compatible con valuetype [ nota:. Esto afecta el comportamiento de los tipos de enumeración, ver II.14.3 partición nota final] "

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

  

El CTS apoya un enum (también conocido como un tipo de enumeración), un nombre alternativo para un tipo existente. A los efectos de Firmas, una enumeración no será el mismo que el tipo subyacente. Las instancias de una enumeración, sin embargo, serán asignables-al tipo subyacente, y viceversa. Es decir, no se requiere ningún yeso (ver §8.3.3) o coacción (véase §8.3.2) convertir de la enumeración al tipo subyacente, ni se les requiere del tipo subyacente a la enumeración. Un enum es considerablemente más restringido que un cierto tipo, como sigue:

     

El tipo subyacente será un tipo entero incorporado. Las enumeraciones se derivan de System.Enum, por lo que son tipos de valor. Al igual que todos los tipos de valores, deberán ser sellados (véase §8.9.9).

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

Esta sentencia será falsa debido a la segunda frase:

x is int

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

A partir 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. A excepción de la firma coincidente (§8.5.2) enumeraciones son tratados como su tipo subyacente. Este subconjunto es el conjunto de tipos de almacenamiento con las enumeraciones eliminado.

Atrás Ir a mi enumeración Foo 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. Se carga una constante. i4 se encuentra en la página 151, que indica el tipo es un entero de 32 bits. No es un reparto!

MSDN :

  

El tipo predeterminado 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 tipo especifica subyacentes la cantidad de almacenamiento se asigna para cada empadronador. Sin embargo, es necesaria una conversión explícita para convertir de tipo de enumeración a un tipo entero.

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.

  • retornos (animal is Enum) true: Por esta razón se puede desempacar animal en una enumeración o evento en un int haciendo una conversión explícita .
  • retornos (animal is int) false: El operador is (en cheque tipo general) no comprueba el tipo subyacente para enumeraciones. También, por esta razón que hay que hacer una conversión explícita para convertir Enum a int.

Si bien los tipos de enumeración se heredan de System.Enum, cualquier conversión entre ellos no es directa, sino un combate de boxeo / unboxing uno. De 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 subyacente   escribir, que debe ser de bytes, sbyte,   resumen, ushort, int, uint o larga   ulong. El conjunto de valores de la   tipo de enumeración es el mismo que el   un conjunto de valores del tipo subyacente.   Los valores del tipo de enumeración no son   restringido a los valores de la llamada   Las constantes. Enumeración son tipos   definido a través de 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.

tipo subyacente de un Enum es del 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
}

A nivel interno, Animal se compone de dos constantes con los valores enteros 0 y 1. Es por eso que se puede convertir explícitamente un entero a una Animal y un Animal a un entero. Si pasa Animal.Dog a un parámetro que acepta un Animal, lo que está haciendo en realidad es pasar el valor de 32 bits entero de Animal.Dog (en este caso, 0). Si se le da Animal un nuevo tipo subyacente, a continuación, los valores se almacenan como ese tipo.

¿Por qué no ... es perfectamente válido, por ejemplo, para una estructura para sostener un int internamente y ser convertibles a int con un operador de conversión explícita ... permite simular una enumeración:

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 utilizar de la misma manera una estructura puede ... por supuesto, si se intenta reflejar el tipo y llamar a la propiedad IsEnum devolverá falso.

Echemos un vistazo a algunas comparaciones uso, con la enumeración equivalentes:

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

La comparación de los usos:

versión Struct:

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

versión de enumeración:

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 quería decir ... Las enumeraciones son especiales, sí ... la cantidad de especial? ¡no mucho! =)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top