【问题标题】:Why does Enum.GetValues() return names when using "var"?为什么使用“var”时 Enum.GetValues() 会返回名称?
【发布时间】:2010-07-09 14:13:58
【问题描述】:

谁能解释一下?

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

【问题讨论】:

  • 当您将鼠标悬停在 var 上时,Visual Studio 会说什么类型?
  • 不知道,但似乎很有用!
  • @sbenderli - 我刚刚查了一下,它是System.Object,这可能在某种程度上解释了差异。
  • @ChrisF,没错。如果您有一个 object 的实例,其值为枚举,那么当 Console.WriteLine() 执行其 .ToString() 时,枚举值将返回其名称,如“已分配”(与其作为字符串的序数值相反,如“ 1")。

标签: c# enums anonymous-types


【解决方案1】:

Enum.GetValues 被声明为返回Array
它返回的数组包含ReportStatus 值形式的实际值。

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

当您迭代 int 时,编译器会将值隐式转换为 int,而 value 变量将保持正常(非装箱)int 值。
因此,Console.WriteLine 调用解析为采用 int 并打印它的重载。

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

【讨论】:

  • 所以返回的底层 IEnumerable 依赖于隐式转换,是吗?
  • 很好的解释,+1。请注意,如果枚举的基础类型是另一个数字类型(例如 uint 或 short),则第二个 foreach 也会失败。
  • @Andreas,在第一个循环中没有进行转换,在第二个循环中,只允许继承转换。也就是说,不是隐式或显式定义的强制转换。
【解决方案2】:

根据the MSDN documentation,采用objectConsole.WriteLine 的重载在其参数上内部调用ToString

当您执行foreach (var value in ...) 时,您的value 变量的类型为object(因为as SLaks points outEnum.GetValues 返回一个无类型的Array),因此您的Console.WriteLine 调用object.ToStringSystem.Enum.ToString 覆盖。此方法返回枚举的名称

当您执行foreach (int value in ...) 时,您将枚举值转换为int 值(而不是object);所以Console.WriteLine 正在调用System.Int32.ToString

【讨论】:

  • 是的,foreach(ReportStatus 编译并打印名称。
【解决方案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() 返回名称是正确的...

【讨论】:

  • 现在一些 s##t 搅拌器会对你发布反汇编代码......无论如何 +1。
  • 我的立场是,如果 MS 是反反汇编的,他们很容易混淆库......
【解决方案4】:

当您使用 Console.WriteLine 时,您在每个元素上隐式调用 ToString()。

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

第一个是 Enum 值 ToString()'ed

【讨论】:

    【解决方案5】:

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

    默认情况下,枚举类型被认为是从 int“派生”的。如果需要,您可以选择从其他整数类型之一派生它,例如字节、短、长等。

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

    在第一个循环中使用 var 关键字告诉编译器使用数组的指定类型 ReportStatus 来确定 value 变量的类型。枚举的 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 循环迭代指定数组的几种不同方法,包括详细描述每种情况下发生的情况的 cmets。

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

    -- 在上面的示例中更新了我的 cmets;经过仔细检查,我发现 System.Array.GetValue 方法实际上使用 TypedReference 类来提取数组的元素并将其作为 System.Object 返回。我最初写过那里发生了拳击操作,但从技术上讲并非如此。我不确定框操作与对 TypedReference.InternalToObject 的调用的比较是什么;我认为这取决于 CLR 实现。无论如何,我相信现在的细节或多或少是正确的。

    【讨论】:

    • 当然,我对 var 成为对象的看法是不正确的,但是 value 变量是一个将枚举值装箱的对象这一事实实际上与行为为何不同的问题无关。我添加了一个代码示例,该示例演示无论 var 是 object 还是 var 是 ReportStatus,行为都是相同的。
    【解决方案6】:

    枚举类型不同于整数。在您的示例中,var 不会计算为 int,而是计算为枚举类型。如果您使用枚举类型本身,您将获得相同的输出。

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

    【讨论】:

    • 关闭,但 var 实际上并不计算为枚举类型;它的计算结果为object(参见 SLaks 的回答)。
    • 从技术上讲,是的,编译器将 var 评估为对象,正如 SLaks 指出的那样,但在运行时该值是枚举类型的(尽管已装箱)。值被装箱的事实与问题所提出的行为无关。
    【解决方案7】:

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

    编辑:
    当您执行Console.WriteLine(value.GetType()) 时,您会看到它确实是一个“报告状态”,尽管它被装在一个普通的Object 中。

    【讨论】:

    • 错了。 var 在此处变为 object
    • 这个答案实际上并没有错,尽管不如 SLaks 的答案那么彻底。 value 变量实际上是 ReportStatus 类型,它只是将 var 评估为 object 的编译器,因为它无法确定 Enum.GetValues 返回的数组的特定类型。但是,在运行时评估 (value is ReportStatus) 会导致结果为真。 value 变量是否是装箱 ReportStatus 的对象,或者它实际上是否是 ReportStatus 实际上与所询问的行为无关。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-07
    • 2011-03-16
    • 2022-01-15
    相关资源
    最近更新 更多