Почему Enum.GetValues ​​() возвращает имена при использовании "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
    }
}

Ответ 1

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

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

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

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

Ответ 2

Согласно документации 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

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(), возвращающем имя, правильно...

Ответ 4

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

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

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

Ответ 5

EDIT: добавлен пример кода, который исследует многие (возможно, все?) возможные способы итерации по массиву.

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

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

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

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

UPDATE: как указывали другие, функция Enum.GetValues ​​возвращает объект, набранный как Array, и в результате он перечислит типы объектов, а не типы ReportStatus.

Независимо от того, конечный результат тот же, что итерация по массиву или 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 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 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 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. Я изначально написал, что там была операция по боксу, но это технически не так. Я не уверен, что сравнение операции с ящиком связано с вызовом TypedReference.InternalToObject; Я предполагаю, что это зависит от реализации CLR. Несмотря на это, я считаю, что детали сейчас более или менее правильны.

Ответ 6

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

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

Ответ 7

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

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