Enum.GetValues() 的内部工作



在阅读某人的源代码时,我遇到了一个C#代码片段,其行为令我感到困惑。在我开始提问之前,这里有一个改编的例子:

枚举:

private enum Seasons
{
Spring,
Summer,
Autumn,
Winter
}

用法:

foreach(Seasons value in Enum.GetValues(typeof(Seasons)))
{
Console.WriteLine(String.Format("{0} {1}", value, (int) value));
}

输出如下所示:

Spring 0
Summer 1
Autumn 2
Winter 3

我的问题:

这在 C# 中是如何工作的?返回值来自:

Enum.GetValues(typeof(Seasons))

是一个填充有值 0-3 的 int 数组。 当它被转换为赛季枚举时,我首先期望它失败。

我认为由于枚举是一种引用类型,在 String.Format()-参数"value"上,将调用 ToString() 方法,该方法将返回其名称。但是当强制转换为 int 时,它将使用该"命名"值的属性并返回 int 值。

如果有人能告诉我幕后是如何工作的,我会很高兴。我浏览了文档,但还无法解决。

谢谢。。。


谢谢大家的回答!

但。。。如果有人能多说一些关于 C# 内部的信息,我将不胜感激。在 Java 中,枚举将是一个真正的引用类型,而在内存中,它将被视为指向存储整数的另一个内存和平的引用。像这样非常非常简化。

---------               -----------------
|       |  Reference    |int     Spring |
|Seasons|-------------> |int     Summer |
|       |               |int     Autumn |
---------               |int     Winter |
-----------------

在我看来,在 C# 中,编译器会将其转移到类似公共 const 的东西(在我的例子中是 int)。请不要扔石头...

可以为基础类型(整数 在这种情况下)。值甚至不限于枚举的命名常量。因此,通过将整数值5转换为Seasons类型来实例化季节值是完全合法的:

Seasons season = (Seasons)5;

更新:首先要注意的是 - System.Enum 是一种值类型(它继承自 ValueType),如 DateTime、Int32 或布尔值。如果您查看为季节枚举定义生成的 IL,您将看到

.class public auto ansi sealed Seasons
extends [mscorlib]System.Enum
{
.field public static literal valuetype Foo.Seasons Autumn = int32(2)
.field public static literal valuetype Foo.Seasons Spring = int32(0)
.field public static literal valuetype Foo.Seasons Summer = int32(1)
.field public specialname rtspecialname int32 value__
.field public static literal valuetype Foo.Seasons Winter = int32(3)
}

因此,这是一组公共静态字段,它们被标记为文字 - 这使得编译器将枚举值直接嵌入到代码中。 例如,以下代码

Seasons season = Seasons.Autumn;
Console.WriteLine(season);

编译后,将只有枚举秋季成员的值:

.locals init (
[0] valuetype Foo.Seasons seasons) // it's value type!
L_0000: nop 
L_0001: ldc.i4.2 // here simple integer value 2 is loaded
L_0002: stloc.0 
L_0003: ldloc.0 
L_0004: box Foo.Seasons 
L_0009: call void [mscorlib]System.Console::WriteLine(object)

因此,实际上您将基础类型的值分配给枚举类型的变量。名称或值没有特殊属性。只是(任何)整数常量。

现在关于获取枚举的所有值 - 当你调用Enum.GetValues它从私有HashtablefieldInfoHash返回值,你可以在Enum类中找到这些值。该Hashtable缓存枚举的名称和值。因此,当您第一次获取枚举的值时,将创建枚举类型的值(ulong数组)和名称(string数组)的HashEntry。实际上,如果您要调用其他Enum方法(如GetNamesIdDefined,则将使用此缓存。

缓存枚举类型的条目后,将返回其值数组。但有一个技巧。这不是简单的ulong值数组。每个值都装箱为带有Enum.ToObject调用的枚举类型(您可以在 System.RuntimeType 中找到实现):

ulong[] values = Enum.InternalGetValues(this);
Array array = Array.UnsafeCreateInstance(this, values.Length);
for (int i = 0; i < values.Length; i++)
{
object obj2 = Enum.ToObject(this, values[i]);
array.SetValue(obj2, i);
}
return array;

这就是为什么每个值对象都是季节类型而不是ulong类型。

Enum.GetValues() 的返回值是一个填充了值 0-3 的 int 数组。当它被投射到赛季枚举时,我首先希望它失败。

不,不是。返回类型为Array,其中包含enumType中常量的值。因此,投掷它是完全合法的。

至于你在那里发现的int值,这是来自MSDN

默认情况下,枚举中每个元素的基础类型为 int。您可以指定另一种整数数值类型。枚举的批准类型包括字节、字节、短、短、整、uint、长整型或长整型。

当您不指定任何编号时,它将分配从您发现的 0 开始的编号。但是,您可以使用任何您想要的数字

private enum Seasons
{
Spring = 10,
Summer = 20,
Autumn = 30,
Winter = 40
}

并将其与数字进行比较

if ((int) Seasons.Spring == 10)

正如文档所说,枚举中每个元素的基础类型都是 int。这是有道理的,因为它是一个枚举。因此,如果将枚举值转换为 int,您将获得相应的 int 值。如果将整数转换为枚举类型,则将获得相应的枚举值。例如,在您的情况下,(Season)2将返回Autumun.

Enum.GetValues(...)函数返回Enum中已隐式转换为查询Enum类型的值的System.Array

这可以像这样确认

var valEnum = Enum.GetValues(typeof(Seasons)).GetEnumerator();
valEnum.MoveNext();
Console.WriteLine(valEnum.Current.GetType());

你会看到

季节

写入控制台。

由于EnumToString()返回成员名称的string,并且当派生Enum成员的int强制转换为int时,它将返回int值,因此代码将按原样工作。


阅读上面的答案后,您可能想知道,我怎么知道以及为什么我关心Enum值是否已隐式转换回System.Array,请考虑一下Enum

enum Seasons
{
Spring = 0,
Summer = 1,
Autumn = 2,
Fall = 2,
Winter = 3
}

如果我这样审问这个Enum

foreach(var e in Enum.GetValues(typeof(Seasons))
{
Console.WriteLine(
"{0}: {1}: {2}",
e.GetType().Name,
e,
(int) e);
}

我得到结果

季节: 春季, 0

季节: 夏季, 1

季节: 秋天, 2

季节: 秋天, 2

季节: 冬季, 3

这可能不是您所期望的。2的第二个值(Fall定义中)已隐式转换为具有匹配值的第一个Seasons成员。在此示例中Autumn.

<小时 />

顺便提一下,测试

var valEnum = Enum.GetValues(typeof(Seasons)).GetEnumerator();
valEnum.MoveNext();
Console.WriteLine(valEnum.Current.GetType().IsValueType);

将输出,

表明Enums是值类型,但与您的问题相比,区别是没有意义的。

最新更新