为什么Enum.GetValues()在使用“var”时会返回名字?

时间:2010-07-09 14:13:59

标签: c# enums anonymous-types

任何人都能解释一下吗?

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

7 个答案:

答案 0 :(得分:40)

Enum.GetValues被宣布为返回Array 它返回的数组包含实际值ReportStatus值。

因此,var关键字变为objectvalue变量保存(已装箱)枚举值。
Console.WriteLine调用解析为在对象上调用object并调用ToString()的重载,对于枚举,返回名称。

当您遍历int时,编译器会隐式地将值转换为intvalue变量保存正常(和非盒装)int值。
因此,Console.WriteLine调用解析为需要int的重载并将其打印出来。

如果您将int更改为DateTime(或任何其他类型),它仍会进行编译,但会在运行时抛出InvalidCastException

答案 1 :(得分:5)

根据the MSDN documentationConsole.WriteLine的重载会使object内部调用ToString

执行foreach (var value in ...)时,您的value变量的输入为object(因为,as SLaks points outEnum.GetValues会返回无类型的Array)因此,Console.WriteLine正在调用object.ToString,而System.Enum.ToString正在覆盖foreach (int value in ...)。此方法返回枚举的名称

当您执行int时,您将枚举值转换为object值(而不是Console.WriteLine);因此System.Int32.ToString正在调用{{1}}。

答案 2 :(得分: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;
}

看起来每个人都说varobject并且调用object.ToString()返回名称是正确的......

答案 3 :(得分:2)

使用Console.WriteLine时,在每个元素上隐含地调用ToString()。

当你说你想要一个int(使用显式类型)时,它会将它转换为int - 然后转换为ToString()它。

第一个是Enum值ToString()'ed

答案 4 :(得分:1)

编辑:添加了一些示例代码,探索了许多(可能是全部?)迭代数组的可能方式。

默认情况下,枚举类型被认为是从int“派生”的。如果需要,您可以选择从其他整数类型派生它,例如byte,short,long等。

在这两种情况下,对Enum.GetValues的调用都返回一个ReportStatus对象数组。

在第一个循环中使用var关键字告诉编译器使用指定的数组类型(ReportStatus)来确定值变量的类型。枚举的ToString实现是返回枚举条目的名称,而不是它所代表的整数值,这就是从第一个循环输出名称的原因。

在第二个循环中使用int变量会导致Enum.GetValues返回的值从ReportStatus隐式转换为int。当然,在int上调用ToString将返回表示整数值的字符串。隐式转换是造成行为差异的原因。

更新:正如其他人所指出的,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实现。无论如何,我相信细节现在或多或少是正确的。

答案 5 :(得分:0)

枚举类型与整数不同。在您的示例中,var不计算为int,它计算为枚举类型。如果您使用了枚举类型本身,则会得到相同的输出。

枚举类型在打印时输出名称,而不是它们的值。

答案 6 :(得分:0)

var value实际上是一个枚举值(类型为ReportStatus),因此您可以看到enumValue.ToString()的标准行为 - 它的名称。

编辑:
当你执行Console.WriteLine(value.GetType())时,你会看到它确实是一个'ReportStatus',虽然它是用普通的Object装箱的。