Pourquoi ne Enum.GetValues () renvoie les noms lors de l'utilisation de "var"?

Quelqu'un Peut-il expliquer cela?

Le texte d'Alt 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
    }
}
27
demandé sur Edward Tanguay 2010-07-09 18:13:58

7 réponses

Enum.GetValues est déclaré comme étant de retour Array.
Le tableau qu'il renvoie contient les valeurs de ReportStatus valeurs.

Par conséquent, le mot clé var devient object, et la variable value contient des valeurs enum typées (encadrées).
L'appel Console.WriteLine résout la surcharge qui prend un object et appelle ToString() sur l'objet, qui, pour les énumérations, renvoie le nom.

Lorsque vous itérez sur un int, le compilateur convertit implicitement les valeurs en int, et la variable value tient valeurs normales (et non encadrées) int.
Par conséquent, l'appel Console.WriteLine résout la surcharge qui prend un int et l'imprime.

Si vous changez int en DateTime (ou tout autre type), il compilera toujours, mais il lancera un InvalidCastException à l'exécution.

39
répondu SLaks 2010-07-09 14:23:02

Selon la documentation MSDN, la surcharge de Console.WriteLine qui prend un object en interne appelle ToString sur son argument.

Lorsque vous faites foreach (var value in ...), votre variable value est tapée object (puisque, comme le souligne SLaks, Enum.GetValues renvoie un Array non typé) et donc votre Console.WriteLine appelle object.ToString qui est remplacé par System.Enum.ToString. Et cette méthode renvoie le nom de l'enum.

Lorsque vous faites foreach (int value in ...), vous convertissez les valeurs enum en int valeurs (au lieu de object); donc Console.WriteLine appelle System.Int32.ToString.

5
répondu Dan Tao 2017-05-23 12:30:52

FWIW, voici le code démonté de Enum.GetValues () (via réflecteur):

[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;
}

Ressemble à ce que tout le monde dit à propos du var étant un object et appelant object.ToString() renvoyant le nom est correct...

3
répondu Tim Coker 2010-07-09 14:28:45

Vous appelez implicitement ToString () sur chaque élément lorsque vous utilisez la Console.WriteLine.

Et quand vous dites que vous voulez un int (en utilisant le type explicite), il le convertira en un int - puis ToString ().

Le premier est la valeur Enum ToString () ' ed

2
répondu Goblin 2010-07-09 14:17:37

EDIT: ajout d'un exemple de code qui explore beaucoup (peut-être tous?) des moyens possibles d'itérer sur le tableau.

Les types Enum sont considérés comme "dérivés" de int par défaut. Vous pouvez choisir de le dériver de l'un des autres types entiers si vous le souhaitez, tels que byte, short, long, etc.

Dans les deux cas, l'appel à Enum.GetValues renvoie un tableau D'objets ReportStatus.

L'Utilisation du mot-clé var dans la première boucle indique au compilateur d'utiliser le type spécifié du tableau, qui est ReportStatus, pour déterminer le type de la variable de valeur. L'implémentation de ToString pour enums est de retourner le nom de l'entrée enum, pas la valeur entière qu'elle représente, c'est pourquoi les noms sont en sortie de la première boucle.

L'utilisation d'une variable int dans la deuxième boucle entraîne la conversion implicite des valeurs renvoyées par Enum.GetValues de ReportStatus en int. Appeler ToString sur un int retournera, bien sûr, une chaîne représentant la valeur entière. L'implicite la conversion est ce qui cause la différence de comportement.

UPDATE: comme d'autres l'ont souligné, L'énumération.La fonction GetValues renvoie un objet typé en tant que Tableau et, par conséquent, il s'agit d'un énumérable de types D'Objets, pas de types ReportStatus.

Indépendamment, le résultat final est le même si itérer sur Array ou 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);
        }
    }
}

Juste pour un peu de plaisir supplémentaire, j'ai ajouté du code ci-dessous démontrant plusieurs façons différentes d'itérer sur le tableau spécifié avec un foreach boucle, y compris les commentaires qui décrit en détail ce qui se passe dans chaque cas.

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);
        }
    }
}

-- mis à jour mes commentaires dans l'exemple ci-dessus; en vérifiant à nouveau, j'ai vu que le système.Tableau.La méthode GetValue utilise en fait la classe TypedReference afin d'extraire un élément du tableau et de le renvoyer en tant que système.Objet. J'avais initialement écrit qu'il y avait une opération de boxe qui s'y passait, mais ce n'est techniquement pas le cas. Je ne sais pas quelle est la comparaison d'une opération de boîte par rapport à un appel à TypedReference.InternalToObject; je suppose que cela dépend de L'implémentation CLR. Quoi qu'il en soit, je crois que les détails sont plus ou moins corrects maintenant.

1
répondu Dr. Wily's Apprentice 2010-07-09 17:17:19

Un type d'énumération est distinct d'un nombre entier. Dans votre échantillon, var n'évalue pas int, il évalue le type d'énumération. Vous obtiendriez la même sortie si vous aviez utilisé le type d'énumération lui-même.

Les types D'énumération affichent le nom lorsqu'ils sont imprimés, pas leur valeur.

0
répondu Shirik 2010-07-09 14:18:20

Le var value est en fait une valeur enum (de type ReportStatus), donc vous voyez le comportement standard de enumValue.ToString () - c'est le nom.

Modifier:
Lorsque vous faites un Console.WriteLine(value.GetType()), vous verrez que c'est vraiment un 'ReportStatus', bien qu'il soit encadré dans une plaine Object.

0
répondu Hans Kesting 2010-07-10 09:21:19