Domanda

Modifica : Commenti in basso. Inoltre, questo .


Ecco cosa una specie di confondermi. La mia comprensione è che se ho un enum come questo ...

enum Animal
{
    Dog,
    Cat
}

... quello che ho fatto essenzialmente è definito un tipo di valore chiamato Animal con due valori definiti, Dog e Cat. Questo tipo deriva dalla tipo di riferimento System.Enum (cosa che i tipi di valore non possono fare normalmente, almeno non in C # -ma che è permesso in questo caso), e ha un impianto per la fusione e indietro per / da valori int.

Se il modo in cui ho appena descritto il tipo enum sopra fosse vero, allora mi aspetterei che il seguente codice di gettare 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, non si può Unbox un tipo di valore da System.Object come qualcosa di diverso il tipo esatto . Così come è possibile che questo possibile? E 'come se il tipo Animal un int (non solo convertibile per int) e un Enum (non solo convertibile per Enum) allo stesso tempo. E 'ereditarietà multipla? Ha System.Enum qualche modo ereditare da System.Int32 (cosa che non mi sarei aspettato di essere possibile)?

Modifica : Non può essere sia di quanto sopra. Il codice seguente illustra questo (credo) in modo conclusivo:

object animal = Animal.Dog;

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

Le uscite precedenti:

True
False

la documentazione MSDN su enumerazioni e la C # specifica fanno uso del termine "tipo sottostante"; ma non so che cosa questo significa, né ho mai sentito usato in riferimento a qualcosa di diverso da enumerazioni. Che cosa significa "tipo sottostante" in realtà media


Quindi, è questo l'ennesimo caso che ottiene un trattamento speciale dal CLR ?

Scommetto su Stando così le cose ... ma una risposta / spiegazione sarebbe bello.


Aggiorna : Damien_The_Unbeliever fornito il riferimento a rispondere a questa domanda veramente. La spiegazione può essere trovata nella partizione II della specifica CLI, nella sezione relativa enumerazioni:

  

A scopo (ad esempio, per la rilegatura   individuazione di un metodo dalla definizione   metodo di riferimento utilizzato per chiamare)   enumerazioni sono distinti dal loro   tipo sottostante. Per tutte le altre   scopi, tra cui la verifica e   esecuzione di codice, un'enumerazione unboxed   interconverts liberamente con la sua   tipo sottostante . Enumerazioni possono essere inscatolati   ad una corrispondente istanza scatolato   tipo, ma questo tipo è non lo stesso   come tipo scatola del sottostante   tipo, in modo da boxe non perde il   tipo originale del enum.

Modifica (di nuovo?!): Aspetta, in realtà, non so che ho letto che a destra la prima volta. Forse non al 100% a spiegare il comportamento unboxing specializzata in sé (anche se sto lasciando la risposta di Damien accettata, in quanto versato una grande quantità di luce su questo tema). Io continuerò cercando in questo ...


Un altro Modifica : L'uomo, quindi di yodaj007 risposta gettato me per un altro ciclo in qualche modo un enum non è esattamente la stessa di un int;.?? ancora un int può essere assegnato ad una variabile enum senza fusione Buh

Credo che tutto questo è in ultima analisi, illuminata da di Hans risposta , ed è per questo che ho accettato. (Scusate, Damien!)

È stato utile?

Soluzione

Sì, un trattamento speciale. Il compilatore JIT è profondamente consapevole del modo in scatola tipi di valore lavoro. Che è, in generale, ciò che rende i tipi di valore che agisce un po 'schizoide. Boxe comporta la creazione di un valore System.Object che si comporta esattamente come un valore di un tipo di riferimento. A quel punto, i valori tipo di valore si comportano più come valori fanno in fase di esecuzione. Che rende possibile, ad esempio, di avere un metodo virtuale come ToString (). L'oggetto scatola ha un puntatore tabella dei metodi, come tipi di riferimento fanno.

Il compilatore JIT conosce i puntatori tavoli metodo per i tipi di valore come int e bool in attacco. Boxing e unboxing per loro è molto efficiente, ci vuole ma una manciata di istruzioni in codice macchina. Questo bisogno di essere di nuovo efficiente in .NET 1.0 per renderlo competitivo. A molto parte importante cioè la restrizione che un valore del tipo di valore può essere solo unboxed allo stesso tipo. Questo evita il jitter di dover generare un'istruzione switch enorme che richiama il codice di conversione corretto. Tutto quello che deve fare è controllare il puntatore tavolo metodo in oggetto e verificare che sia del tipo previsto. E copiare il valore fuori l'oggetto direttamente. Notevole forse è che questa limitazione non esiste in VB.NET, il suo operatore CType () in effetti generare il codice per una funzione di supporto che contiene questo grande dichiarazione switch.

Il problema con i tipi Enum è che questo non può funzionare. Enums possono avere un diverso tipo GetUnderlyingType (). In altre parole, il valore unboxed ha diverse dimensioni in modo semplicemente copiando il valore fuori dell'oggetto scatola non può funzionare. Profondamente consapevoli, il jitter non in linea il codice unboxing più, genera una chiamata a una funzione di supporto nel CLR.

Questo aiuto si chiama JIT_Unbox (), è possibile trovare il suo codice sorgente nella fonte SSCLI20, CLR / src / vm / jithelpers.cpp. Vedrete che fare con tipi enum appositamente. È permissivo, permette unboxing da un tipo enum all'altro. Ma solo se il tipo di fondo è lo stesso, si ottiene un InvalidCastException se questo non è il caso.

che è anche il motivo per cui Enum viene dichiarata come una classe. La sua logico è il comportamento di un tipo di riferimento, tipi enum derivati ??possono essere lanciati da uno all'altro. Con la limitazione sopra indicato sulla compatibilità tipo sottostante. I valori di un tipo enum hanno tuttavia molto il comportamento di un valore tipo valore. Hanno la semantica di copia e il comportamento di pugilato.

Altri suggerimenti

enumerazioni sono appositamente trattate dal CLR. Se si vuole entrare nei dettagli scabrosi, è possibile scaricare il MS partizione II spec. In esso, troverete che enumerazioni:

  

enumerazioni Obey ulteriori restrizioni   al di là di quelli di altri tipi di valore.   Enumerazioni contengono solo campi come   membri (che non devono nemmeno definire   digitare inizializzatori o istanza   costruttori); essi non devono   implementare le interfacce; essi   avrà disposizione in campo auto   (§10.1.2); essi devono avere esattamente un   campo istanza e deve essere del tipo sottostante di   l'enumerazione; tutti gli altri campi devono essere   statico e letterale (§16.1);

Ecco, questo è il modo in cui può ereditare da System.Enum, ma hanno un tipo di "sottostante" -. È il campo di istanza singola che stanno permesso di avere

C'è anche una discussione sul comportamento di pugilato, ma non descrive in modo esplicito unboxing al tipo di fondo, che posso vedere.

Partizione I, 8.5.2 afferma che enums sono "un nome alternativo per un tipo esistente" ma "[a] i fini di firme, un'enumerazione non deve essere lo stesso del tipo sottostante."

Partizione II, 14.3 espone: ". Per tutti gli altri scopi, compresa la verifica e l'esecuzione di codice, un'enumerazione unboxed liberamente interconverts con il suo tipo sottostante Enums possono essere inscatolati ad un corrispondente tipo di istanza scatolato, ma questo tipo non è la stessa come tipo in scatola del tipo sottostante, in modo da boxe non perde il tipo originale del enum. "

Partizione III, 4,32 spiega il comportamento unboxing:. "Il tipo di tipo di valore contenuto all'interno obj deve essere incarico compatibile con valuetype [ Nota:. Questo effetti il ??comportamento con i tipi enum, vedono partizione II.14.3 nota fine] "

Quello che sto notando qui è da pagina 38 della ECMA-335 (vi consiglio di scaricarlo solo per averlo):

  

Il CTS supporta un enum (noto anche come un tipo di enumerazione), un nome alternativo per un tipo esistente. Ai fini di firme, un'enumerazione non è lo stesso del tipo sottostante. Istanze di un enum, tuttavia, sono classificabili-al tipo sottostante, e viceversa. Cioè, non cast (vedi §8.3.3) o coercizione (vedi §8.3.2) è necessario per convertire da enum al tipo sottostante, né sono tenuti dal tipo sottostante al enum. Un enum è notevolmente più ristretto di un tipo di vero, come segue:

     

Il tipo sottostante è di tipo intero incorporato. Enums si derivano da System.Enum, quindi sono tipi di valore. Come tutti i tipi di valore, devono essere sigillati (vedi §8.9.9).

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

Questa affermazione sarà falso a causa della seconda frase:

x is int

Si sono lo stesso (un alias), ma la loro firma non è la stessa. Convertire da e verso un int non è un cast.

Da pagina 46:

  

tipi sottostanti - nelle enumerazioni CTS sono nomi alternativi per i tipi esistenti (§8.5.2), definita la loro tipo sottostante. Fatta eccezione per la corrispondenza di firma (§8.5.2) enumerazioni sono trattati come il loro tipo sottostante. Questo sottoinsieme è l'insieme di tipi di archiviazione con le elencazioni rimossi.

Torna al mio Foo enum in precedenza. Questa dichiarazione funziona:

Foo x = (Foo)5;

Se si controlla il codice IL generato del mio metodo principale in 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 non c'è cast. ldc si trova a pagina 86. Si carica una costante. i4 è a pagina 151, che indica il tipo è un intero di 32 bit. Non c'è un cast!

MSDN :

  

Il tipo predefinito base degli elementi di enumerazione è int. Per default, la prima enumeratore ha il valore 0, e il valore di ogni enumeratore successivo viene incrementato di 1.

Quindi, il cast è possibile, ma è necessario forzare:

  

tipo specifica sottostanti quanta memoria allocata per ogni enumeratore. Tuttavia, è necessario un cast esplicito convertire da tipo enum a un tipo integrale.

Quando si Box vostra enum in object, l'oggetto degli animali deriva dal System.Enum (il tipo reale è noto in fase di esecuzione), quindi è in realtà un int, in modo che il cast è valido.

  • torna (animal is Enum) true: Per questo motivo è possibile unboxing animale in un Enum o evento in un int facendo un cast esplicito
  • .
  • ritorni (animal is int) false: L'operatore is (arrivo tipo generale) non controlla il tipo sottostante per Enums. Inoltre, per questo motivo è necessario fare un cast esplicito per convertire Enum a int.

Mentre tipi enum vengono ereditate dal System.Enum, qualsiasi conversione tra loro non è diretta, ma un incontro di boxe / un unboxing. Da C # 3.0 Specification:

  

Tipo di elencazione è un tipo distinto   con costanti denominate. Ogni   tipo di enumerazione ha un sottostante   tipo, che deve essere byte, sbyte,   Insomma, ushort, int, uint, lungo o   ulong. L'insieme dei valori della   tipo enumerazione è lo stesso del   insieme di valori del tipo sottostante.   Valori del tipo di enumerazione non sono   limitata ai valori del nome   costanti. Enumeration tipi sono   definito attraverso censimento   Dichiarazioni

Così, mentre la classe degli animali è derivato da System.Enum, è in realtà un int. Btw, un'altra cosa strana è System.Enum è derivato da System.ValueType, tuttavia è ancora un tipo di riferimento.

tipo sottostante di un Enum è il tipo utilizzato per memorizzare il valore delle costanti. Nel tuo esempio, anche se non si è esplicitamente definito i valori, C # fa questo:

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

Internamente, Animal si compone di due costanti con i valori interi 0 e 1. È per questo che si può lanciare in modo esplicito un numero intero a un Animal ed un Animal ad un numero intero. Se si passa Animal.Dog ad un parametro che accetta un Animal, ciò che si sta realmente facendo è passando il valore a 32 bit intero di Animal.Dog (in questo caso, 0). Se si dà Animal un nuovo tipo di fondo, quindi i valori vengono memorizzati come quel tipo.

Perché non ... è perfettamente valido, ad esempio, per una struttura di tenere un int internamente, e essere convertibile in int con un operatore cast esplicito ... permette di simulare 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();
    }
}

Questa struct può essere utilizzato tutto allo stesso modo una struct può ... ovviamente, se si tenta di riflettere il tipo, e chiamare proprietà IsEnum tornerà falso.

Diamo un'occhiata ad alcuni confronto utilizzo, con l'enumerazione equivalente:

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

Confronto usi:

Versione Struct:

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

Versione Enum:

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

Alcune operazioni non possono essere simulati, ma questo tutti gli spettacoli quello che volevo dire ... enumerazioni sono speciali, sì ... quanto speciale? non molto! =)

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top