Почему Enum.GetValues ​​() возвращает имена при использовании «var»? - PullRequest
27 голосов
/ 09 июля 2010

Может кто-нибудь объяснить это?

альтернативный текст 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
    }
}

Ответы [ 7 ]

40 голосов
/ 09 июля 2010

Enum.GetValues объявлено как возвращающее Array.
Возвращаемый массив содержит действительные значения в виде ReportStatus значений.

Следовательно, ключевое слово var становится object, а переменная value содержит типизированные (в штучной упаковке) значения перечисления.
Вызов Console.WriteLine разрешает перегрузку, которая принимает object и вызывает ToString() объекта, который для перечислений возвращает имя.

Когда вы перебираете int, компилятор неявно преобразует значения в int, а переменная value содержит нормальные (и не упакованные) значения int.
Следовательно, вызов Console.WriteLine разрешает перегрузку, которая принимает int и печатает его.

Если вы измените int на DateTime (или любой другой тип), он все равно будет компилироваться, но он выдаст InvalidCastException во время выполнения.

5 голосов
/ 09 июля 2010

Согласно документации MSDN , перегрузка Console.WriteLine, которая принимает object внутренне, вызывает ToString в своем аргументе.

Когда вы делаете foreach (var value in ...), ваша переменная value печатается как object (поскольку, , как указывает SLaks , Enum.GetValues возвращает нетипизированный Array ) и поэтому ваш Console.WriteLine вызывает object.ToString, который переопределяется на System.Enum.ToString. И этот метод возвращает имя перечисления.

Когда вы делаете foreach (int value in ...), вы приводите значения перечисления к int значениям (вместо object); поэтому Console.WriteLine звонит System.Int32.ToString.

3 голосов
/ 09 июля 2010

FWIW, вот дизассемблированный код из Enum.GetValues ​​() (через 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;
}

Похоже, что все говорят о том, что var является object и вызывает object.ToString(), возвращая имя правильно ...

2 голосов
/ 09 июля 2010

Вы неявно вызываете ToString () для каждого элемента, когда используете Console.WriteLine.

И когда вы говорите, что хотите использовать int (используя явный тип), он преобразует его в int, а затем ToString ().

Первым является значение Enum ToString () 'ed

1 голос
/ 09 июля 2010

РЕДАКТИРОВАТЬ: добавлен пример кода, который исследует много (возможно, все?) Возможных способов перебора массива.

Типы перечислений по умолчанию считаются «производными» от int. При желании вы можете получить его из одного из целочисленных типов, таких как байты, короткие, длинные и т. Д.

В обоих случаях вызов Enum.GetValues возвращает массив объектов ReportStatus.

Использование ключевого слова var в первом цикле указывает компилятору использовать указанный тип массива, а именно ReportStatus, для определения типа переменной-значения. Реализация ToString для перечислений должна возвращать имя записи перечисления, а не целочисленное значение, которое она представляет, поэтому имена выводятся из первого цикла.

Использование переменной int во втором цикле приводит к неявному преобразованию значений, возвращаемых Enum.GetValues, из ReportStatus в int. Вызов ToString для int, конечно, вернет строку, представляющую целочисленное значение. Неявное преобразование - это то, что вызывает разницу в поведении.

ОБНОВЛЕНИЕ: Как уже отмечали другие, функция Enum.GetValues ​​возвращает объект, типизированный как Array, и в результате он является перечисляемым типом Object, а не типом ReportStatus.

Несмотря на это, конечный результат один и тот же, независимо от того, итерируете ли вы по Array или 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);
        }
    }
}

Просто для дополнительного удовольствия я добавил нижеприведенный код, демонстрирующий несколько различных способов перебора указанного массива с помощью цикла foreach, включая комментарии, в которых подробно описывается, что происходит в каждом случае.

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

- обновлены мои комментарии в примере выше; после двойной проверки я увидел, что метод System.Array.GetValue фактически использует класс TypedReference, чтобы извлечь элемент массива и вернуть его как System.Object. Первоначально я написал, что там происходила операция по боксу, но технически это не так. Я не уверен, что сравнение операции box против вызова TypedReference.InternalToObject; Я предполагаю, что это зависит от реализации CLR. Несмотря на это, я считаю, что сейчас детали более или менее верны.

0 голосов
/ 09 июля 2010

var value на самом деле является значением перечисления (типа ReportStatus), поэтому вы видите стандартное поведение enumValue.ToString () - это имя.

EDIT:
Когда вы сделаете Console.WriteLine(value.GetType()), вы увидите, что это действительно ReportStatus, хотя оно упаковано в простой Object.

0 голосов
/ 09 июля 2010

Тип перечисления отличается от целого числа. В вашем примере var не оценивается как int, оно оценивается как тип перечисления. Вы бы получили тот же вывод, если бы использовали сам тип перечисления.

Типы перечисления выводят имя при печати, а не их значение.

...