2010-07-09 9 views
27

Qualcuno può spiegarlo?Perché Enum.GetValues ​​() restituisce i nomi quando si utilizza "var"?

alt text http://www.deviantsart.com/upload/g4knqc.png

using System; 

namespace TestEnum2342394834 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      //with "var" 
      foreach (var value in Enum.GetValues(typeof(ReportStatus))) 
      { 
       Console.WriteLine(value); 
      } 

      //with "int" 
      foreach (int value in Enum.GetValues(typeof(ReportStatus))) 
      { 
       Console.WriteLine(value); 
      } 

     } 
    } 

    public enum ReportStatus 
    { 
     Assigned = 1, 
     Analyzed = 2, 
     Written = 3, 
     Reviewed = 4, 
     Finished = 5 
    } 
} 
+0

Che cosa dice il tipo di Visual Studio quando si passa il mouse su 'var'? – ChrisF

+0

Nessuna idea, ma sembra molto utile! – sbenderli

+0

@sbenderli - Ho appena controllato ed è 'System.Object', che probabilmente spiega in qualche modo la differenza. – ChrisF

risposta

39

Enum.GetValues è dichiarato come il ritorno Array.
La matrice restituita contiene i valori attuali come valori ReportStatus.

Di conseguenza, la parola chiave var diventa object e la variabile value contiene valori enum scritti (in box).
La chiamata Console.WriteLine risolve il sovraccarico che prende un object e chiama ToString() sull'oggetto, che, per enumerazioni, restituisce il nome.

Quando eseguire iterazioni su un int, il compilatore getta implicitamente i valori da int, e la variabile value stive normale (e non in scatola) int valori.
Pertanto, la chiamata Console.WriteLine risolve il sovraccarico che prende uno int e lo stampa.

Se si modifica int a DateTime (o qualsiasi altro tipo), verrà comunque compilato, ma verrà generato un InvalidCastException in fase di esecuzione.

+0

Quindi l'IEnumerable sottostante restituito si basa sulle conversioni implicite, vero? – Andreas

+3

Buona spiegazione, +1. Notare che se il tipo sottostante dell'enumerazione era un altro tipo numerico (uint o short per esempio), anche il secondo foreach avrebbe esito negativo. –

+0

@Andreas, non vi è alcuna conversione in corso nel primo ciclo e sul secondo ciclo è consentita solo la trasmissione dell'ereditarietà. Cioè, non cast esplicito o definito esplicitamente. – Dykam

5

Secondo the MSDN documentation, il sovraccarico di Console.WriteLine che accetta un object chiama internamente ToString sul suo argomento.

Quando si esegue foreach (var value in ...), la variabile value è tipizzato come object (dal momento che, as SLaks points out, Enum.GetValues restituisce un non tipizzato Array) e così la vostra Console.WriteLine sta chiamando object.ToString che viene sovrascritto da System.Enum.ToString. E questo metodo restituisce il nome dell'enumerazione.

Quando si esegue foreach (int value in ...), si stanno trasmettendo i valori enum ai valori int (anziché object); quindi Console.WriteLine sta chiamando System.Int32.ToString.

+0

Sì, il 'foreach (ReportStatus' compila e stampa i nomi. –

2

Si chiama in modo implicito ToString() su ciascun elemento quando si utilizza Console.WriteLine.

E quando dici che vuoi un int (usando il tipo esplicito) lo convertirà in un int - e poi in ToString().

Il primo è il valore ToString Enum() 'cato

0

Tipo di elencazione è distinto da un numero intero. Nel tuo esempio, var non valuta to int, valuta il tipo di enumerazione. Otterresti lo stesso risultato se avessi usato il tipo di enumerazione stesso.

I tipi di enumerazione emettono il nome quando stampati, non il loro valore.

+2

Chiudi, ma il' var' non valuta il tipo di enumerazione, in realtà; valuta a 'oggetto' (vedere la risposta di SLaks). –

+2

Tecnicamente, true, il compilatore valuta var come oggetto, come indicato da SLaks, ma in fase di esecuzione il valore è del tipo di enumerazione (sebbene in box) .Il fatto che il valore sia inscatolato è irrilevante per il comportamento che viene posto nella domanda –

0

var value è in realtà un valore enum (di tipo ReportStatus), quindi viene visualizzato il comportamento standard di enumValue.ToString(), il suo nome.

EDIT:
Quando si esegue un Console.WriteLine(value.GetType()) vedrete che è davvero un 'ReportStatus', anche se è confezionato in una pianura Object.

+1

Wrong. diventa 'object' qui. – SLaks

+0

Questa risposta non è in realtà sbagliata, sebbene non così accurata come la risposta di SLaks. La variabile value è in realtà di tipo ReportStatus, è solo il compilatore che valuta var per oggetto perché non è in grado di determinare il tipo specifico dell'array restituito da Enum.GetValues. Tuttavia, la valutazione di '(valore è ReportStatus)' ai risultati di runtime è vera. Se o non la variabile value è un oggetto che inscatola un ReportStatus o se in realtà è un ReportStatus in realtà irrilevante per il comportamento richiesto. –

3

FWIW, ecco il codice disassemblato da Enum.GetValues ​​() (via Reflector):

[ComVisible(true)] 
public static Array GetValues(Type enumType) 
{ 
    if (enumType == null) 
    { 
     throw new ArgumentNullException("enumType"); 
    } 
    if (!(enumType is RuntimeType)) 
    { 
     throw new ArgumentException(Environment.GetResourceString("Arg_MustBeType"), "enumType"); 
    } 
    if (!enumType.IsEnum) 
    { 
     throw new ArgumentException(Environment.GetResourceString("Arg_MustBeEnum"), "enumType"); 
    } 
    ulong[] values = GetHashEntry(enumType).values; 
    Array array = Array.CreateInstance(enumType, values.Length); 
    for (int i = 0; i < values.Length; i++) 
    { 
     object obj2 = ToObject(enumType, values[i]); 
     array.SetValue(obj2, i); 
    } 
    return array; 
} 

Sembra che quello che tutti sta dicendo sulla var essere un object e chiamando object.ToString() restituire il nome è corretto ...

+0

Ora un po 'di agitazione ti avrà per pubblicare un codice smontato ... +1 comunque. – Mau

+0

La mia posizione su tutto ciò che è se MS fosse anti-disassemblaggio, potevano facilmente offuscare le librerie ... –

1

EDIT: aggiunto un codice di esempio che esplora molti (forse tutti?) Possibili modi di iterare sull'array.

I tipi di enum sono considerati "derivati" da int di default. È possibile scegliere di derivarlo da uno degli altri tipi interi se lo si desidera, ad esempio byte, breve, lungo, ecc.

In entrambi i casi, la chiamata a Enum.GetValues restituisce una matrice di oggetti ReportStatus.

L'utilizzo della parola chiave var nel primo ciclo indica al compilatore di utilizzare il tipo specificato dell'array, che è ReportStatus, per determinare il tipo della variabile valore. L'implementazione di ToString per enumerazione restituisce il nome della voce enum, non il valore intero che rappresenta, motivo per cui i nomi vengono emessi dal primo ciclo.

Utilizzando una variabile int nel secondo ciclo, i valori restituiti da Enum.GetValues vengono convertiti implicitamente da ReportStatus a int. Chiamare ToString su un int, ovviamente, restituisce una stringa che rappresenta il valore intero. La conversione implicita è ciò che causa la differenza di comportamento.

AGGIORNAMENTO: Come altri hanno sottolineato, la funzione Enum.GetValues ​​restituisce un oggetto digitato come Array e, di conseguenza, è un enumerabile di tipi di oggetto, non di tipi di ReportStatus.

Indipendentemente da ciò, il risultato finale è lo stesso se l'iterazione di Array o ReportStatus []:

class Program 
{ 
    enum ReportStatus 
    { 
     Assigned = 1, 
     Analyzed = 2, 
     Written = 3, 
     Reviewed = 4, 
     Finished = 5, 
    } 

    static void Main(string[] args) 
    { 
     WriteValues(Enum.GetValues(typeof(ReportStatus))); 

     ReportStatus[] values = new ReportStatus[] { 
      ReportStatus.Assigned, 
      ReportStatus.Analyzed, 
      ReportStatus.Written, 
      ReportStatus.Reviewed, 
      ReportStatus.Finished, 
     }; 

     WriteValues(values); 
    } 

    static void WriteValues(Array values) 
    { 
     foreach (var value in values) 
     { 
      Console.WriteLine(value); 
     } 

     foreach (int value in values) 
     { 
      Console.WriteLine(value); 
     } 
    } 

    static void WriteValues(ReportStatus[] values) 
    { 
     foreach (var value in values) 
     { 
      Console.WriteLine(value); 
     } 

     foreach (int value in values) 
     { 
      Console.WriteLine(value); 
     } 
    } 
} 

Solo per un po 'di divertimento in più, ho aggiunto un po' di codice di seguito dimostrando diversi modi di scorrere il specificato array con un ciclo foreach, compresi i commenti che descrivono in dettaglio cosa succede in ogni caso.

class Program 
{ 
    enum ReportStatus 
    { 
     Assigned = 1, 
     Analyzed = 2, 
     Written = 3, 
     Reviewed = 4, 
     Finished = 5, 
    } 

    static void Main(string[] args) 
    { 
     Array values = Enum.GetValues(typeof(ReportStatus)); 

     Console.WriteLine("Type of array: {0}", values.GetType().FullName); 

     // Case 1: iterating over values as System.Array, loop variable is of type System.Object 
     // The foreach loop uses an IEnumerator obtained from System.Array. 
     // The IEnumerator's Current property uses the System.Array.GetValue method to retrieve the current value, which uses the TypedReference.InternalToObject function. 
     // The value variable is passed to Console.WriteLine(System.Object). 
     // Summary: 0 box operations, 0 unbox operations, 1 usage of TypedReference 
     Console.WriteLine("foreach (object value in values)"); 
     foreach (object value in values) 
     { 
      Console.WriteLine(value); 
     } 

     // Case 2: iterating over values as System.Array, loop variable is of type ReportStatus 
     // The foreach loop uses an IEnumerator obtained from System.Array. 
     // The IEnumerator's Current property uses the System.Array.GetValue method to retrieve the current value, which uses the TypedReference.InternalToObject function. 
     // The current value is immediatly unboxed as ReportStatus to be assigned to the loop variable, value. 
     // The value variable is then boxed again so that it can be passed to Console.WriteLine(System.Object). 
     // Summary: 1 box operation, 1 unbox operation, 1 usage of TypedReference 
     Console.WriteLine("foreach (ReportStatus value in values)"); 
     foreach (ReportStatus value in values) 
     { 
      Console.WriteLine(value); 
     } 

     // Case 3: iterating over values as System.Array, loop variable is of type System.Int32. 
     // The foreach loop uses an IEnumerator obtained from System.Array. 
     // The IEnumerator's Current property uses the System.Array.GetValue method to retrieve the current value, which uses the TypedReference.InternalToObject function. 
     // The current value is immediatly unboxed as System.Int32 to be assigned to the loop variable, value. 
     // The value variable is passed to Console.WriteLine(System.Int32). 
     // Summary: 0 box operations, 1 unbox operation, 1 usage of TypedReference 
     Console.WriteLine("foreach (int value in values)"); 
     foreach (int value in values) 
     { 
      Console.WriteLine(value); 
     } 

     // Case 4: iterating over values as ReportStatus[], loop variable is of type System.Object. 
     // The foreach loop is compiled as a simple for loop; it does not use an enumerator. 
     // On each iteration, the current element of the array is assigned to the loop variable, value. 
     // At that time, the current ReportStatus value is boxed as System.Object. 
     // The value variable is passed to Console.WriteLine(System.Object). 
     // Summary: 1 box operation, 0 unbox operations 
     Console.WriteLine("foreach (object value in (ReportStatus[])values)"); 
     foreach (object value in (ReportStatus[])values) 
     { 
      Console.WriteLine(value); 
     } 

     // Case 5: iterating over values as ReportStatus[], loop variable is of type ReportStatus. 
     // The foreach loop is compiled as a simple for loop; it does not use an enumerator. 
     // On each iteration, the current element of the array is assigned to the loop variable, value. 
     // The value variable is then boxed so that it can be passed to Console.WriteLine(System.Object). 
     // Summary: 1 box operation, 0 unbox operations 
     Console.WriteLine("foreach (ReportStatus value in (ReportStatus[])values)"); 
     foreach (ReportStatus value in (ReportStatus[])values) 
     { 
      Console.WriteLine(value); 
     } 

     // Case 6: iterating over values as ReportStatus[], loop variable is of type System.Int32. 
     // The foreach loop is compiled as a simple for loop; it does not use an enumerator. 
     // On each iteration, the current element of the array is assigned to the loop variable, value. 
     // The value variable is passed to Console.WriteLine(System.Int32). 
     // Summary: 0 box operations, 0 unbox operations 
     Console.WriteLine("foreach (int value in (ReportStatus[])values)"); 
     foreach (int value in (ReportStatus[])values) 
     { 
      Console.WriteLine(value); 
     } 

     // Case 7: The compiler evaluates var to System.Object. This is equivalent to case #1. 
     Console.WriteLine("foreach (var value in values)"); 
     foreach (var value in values) 
     { 
      Console.WriteLine(value); 
     } 

     // Case 8: The compiler evaluates var to ReportStatus. This is equivalent to case #5. 
     Console.WriteLine("foreach (var value in (ReportStatus[])values)"); 
     foreach (var value in (ReportStatus[])values) 
     { 
      Console.WriteLine(value); 
     } 
    } 
} 

- Aggiornato i miei commenti nell'esempio sopra; dopo un doppio controllo ho visto che il metodo System.Array.GetValue utilizza effettivamente la classe TypedReference per estrarre un elemento dell'array e restituirlo come System.Object. Originariamente avevo scritto che c'era un'operazione di inscatolamento, ma tecnicamente non è il caso. Non sono sicuro di quale sia il confronto tra un'operazione di una casella e una chiamata a TypedReference.InternalToObject; Presumo che dipenda dall'implementazione CLR. Indipendentemente da ciò, credo che i dettagli siano più o meno corretti ora.

+0

Come molte altre risposte, sbagliato. 'var' diventa' oggetto'. – SLaks

+1

Concesso, non ero corretto su var diventando oggetto, ma il fatto che la variabile value sia un oggetto che racchiude i valori enum è in realtà irrilevante per la domanda sul perché il comportamento è diverso. Ho aggiunto un esempio di codice che dimostra che il comportamento è lo stesso indipendentemente dal fatto che var sia object o var sia ReportStatus. –

Problemi correlati