2010-07-09 23 views
27

¿Alguien puede explicar esto?¿Por qué Enum.GetValues ​​() devuelve nombres cuando se usa "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

¿Qué dice el tipo de Visual Studio cuando pasas el mouse sobre la 'var'? – ChrisF

+0

¡No tengo idea, pero me parece muy útil! – sbenderli

+0

@sbenderli - Acabo de comprobar y es 'System.Object', que probablemente de alguna manera explica la diferencia. – ChrisF

Respuesta

39

Enum.GetValues se declara como devolver Array.
La matriz que devuelve contiene los valores reales como valores ReportStatus.

Por lo tanto, la palabra clave var se convierte en object, y la variable value contiene valores de enum tipados (en recuadro).
La llamada Console.WriteLine resuelve la sobrecarga que toma un object y llama al ToString() en el objeto, que devuelve el nombre para enums.

Al iterar sobre una int, el compilador implícitamente arroja los valores a int, y la variable value mantiene normal (y no en caja) int valores.
Por lo tanto, la llamada Console.WriteLine resuelve la sobrecarga que toma un int y la imprime.

Si cambia int a DateTime (o cualquier otro tipo), aún se compilará, pero lanzará un InvalidCastException en tiempo de ejecución.

+0

Entonces, el subyacente IEnumerable depende de conversiones implícitas, ¿o sí? – Andreas

+3

Buena explicación, +1. Tenga en cuenta que si el tipo subyacente de la enumeración fuera otro tipo numérico (uint o short, por ejemplo), el segundo foreach también fallaría. –

+0

@Andreas, no hay conversión en el primer ciclo, y en el segundo ciclo, solo se permite la conversión de herencia. Es decir, no se trata de moldes implícita o explícitamente definidos. – Dykam

5

De acuerdo con the MSDN documentation, la sobrecarga de Console.WriteLine que toma un object internamente llama a ToString en su argumento.

Al hacer foreach (var value in ...), la variable de value se escribe como object (ya que, as SLaks points out, Enum.GetValues devuelve un Array sin tipo) y por lo que su Console.WriteLine está llamando object.ToString que está anulado por System.Enum.ToString. Y este método devuelve el nombre de la enumeración.

Cuando hace foreach (int value in ...), está arrojando los valores enum a valores int (en lugar de object); entonces Console.WriteLine llama al System.Int32.ToString.

+0

Sí, el 'foreach (ReportStatus' compila e imprime los nombres. –

2

Llamarás implícitamente a ToString() en cada elemento cuando uses Console.WriteLine.

Y cuando diga que quiere un int (usando el tipo explícito) lo convertirá en un int - y luego en ToString().

El primero es el valor ToString Enum() 'ed

0

un tipo de enumeración es distinto de un número entero. En su muestra, var no evalúa a int, se evalúa como el tipo de enumeración. Obtendrá el mismo resultado si hubiera utilizado el tipo de enumeración en sí.

Los tipos de enumeración dan como resultado el nombre cuando se imprime, no su valor.

+2

Close, pero la' var' no evalúa al tipo de enumeración, en realidad, se evalúa como 'object' (vea la respuesta de SLaks). –

+2

Técnicamente, cierto, el compilador evalúa var al objeto como señala SLaks, pero en tiempo de ejecución el valor es del tipo de enumeración (aunque encuadrado). El hecho de que el valor esté encuadrado es irrelevante para el comportamiento que se formula con la pregunta. –

0

var value es en realidad un valor enum (del tipo ReportStatus), por lo que puede ver el comportamiento estándar de enumValue.ToString() - es su nombre.

EDIT:
Cuando haces un Console.WriteLine(value.GetType()) verá que en realidad es un 'ReportStatus', aunque está encajonado en una llanura Object.

+1

Incorrecto. se convierte en 'objeto' aquí – SLaks

+0

Esta respuesta no es realmente incorrecta, aunque no tan completa como la respuesta de SLaks. La variable de valor ES en realidad de tipo ReportStatus, es solo el compilador que evalúa var a objeto porque no puede determinar el tipo específico de la matriz devuelta por Enum.GetValues. Sin embargo, evaluar '(el valor es ReportStatus)' en el tiempo de ejecución da como resultado verdadero. Independientemente de si la variable de valor es un objeto que encajona un ReportStatus o si realmente es un ReportStatus que en realidad es irrelevante para el comportamiento que se está preguntando. –

3

Fwiw, aquí está el código desensamblado de Enum.GetValues ​​() (a través del 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; 
} 

parece a lo que todo el mundo está diciendo sobre el var ser un object y llamando object.ToString() devolver el nombre es correcto ...

+0

Ahora un poco de agitador te va a tener que enviar un código desensamblado ... +1 de todos modos. – Mau

+0

Mi posición en todo lo que es si la MS fuera antidesmontaje, podrían ofuscar fácilmente las bibliotecas ... –

1

EDITAR: se agregó un código de muestra que explora muchas (¿quizás todas?) Formas posibles de iterar sobre la matriz.

Los tipos de enum se consideran "derivados" de int por defecto. Puede elegir derivarlo de uno de los otros tipos de enteros si lo desea, como byte, corto, largo, etc.

En ambos casos, la llamada a Enum.GetValues devuelve una matriz de objetos ReportStatus.

El uso de la palabra clave var en el primer ciclo le dice al compilador que use el tipo especificado de la matriz, que es ReportStatus, para determinar el tipo de la variable de valor. La implementación de ToString para enums es devolver el nombre de la entrada enum, no el valor entero que representa, por lo que los nombres se están produciendo desde el primer ciclo.

El uso de una variable int en el segundo ciclo hace que los valores devueltos por Enum.GetValues se conviertan implícitamente de ReportStatus a int. Llamar a ToString en un int., Por supuesto, devolverá una cadena que representa el valor entero. La conversión implícita es lo que causa la diferencia en el comportamiento.

ACTUALIZACIÓN: Como otros han señalado, la función Enum.GetValues ​​devuelve un objeto escrito como matriz, y como resultado es un enumerable de tipos de objeto, no tipos de estado de informe.

En cualquier caso, el resultado final es el mismo si la iteración en matriz 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); 
     } 
    } 
} 

Sólo por poco de diversión extra, he añadido algo de código a continuación demuestra varias maneras diferentes de la iteración en la especificada array con un ciclo foreach, que incluye comentarios que describen en detalle lo que está sucediendo en cada 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); 
     } 
    } 
} 

- He actualizado mis comentarios en el ejemplo anterior; al hacer una doble comprobación, vi que el método System.Array.GetValue realmente utiliza la clase TypedReference para extraer un elemento de la matriz y devolverlo como System.Object. Originalmente había escrito que había una operación de boxeo allí, pero técnicamente no es el caso. No estoy seguro de cuál es la comparación de una operación de cuadro con una llamada a TypedReference.InternalToObject; Supongo que depende de la implementación de CLR. A pesar de todo, creo que los detalles son más o menos correctos ahora.

+0

Como la mayoría de las otras respuestas, mal. 'var' se convierte en' objeto'. – SLaks

+1

De acuerdo, era incorrecto que var se convirtiera en objeto, pero el hecho de que la variable de valor sea un objeto que encapsula los valores enum es realmente irrelevante para la pregunta de por qué el comportamiento es diferente. He agregado un ejemplo de código que demuestra que el comportamiento es el mismo independientemente de si var es object o var es ReportStatus. –

Cuestiones relacionadas