关于C#:.NET枚举类型实际上是可变值类型吗?

Are .NET enum types actually mutable value types?

回想一下枚举类型的字段,我惊讶地注意到,持有枚举特定实例的实际值的"backing"实例字段不是我想象中的private,而是public。也不是江户十一〔二〕号。(IsPublic对,IsInitOnly错。)

许多人认为.NET类型系统中的"可变"值类型是"邪恶的",那么为什么枚举类型(例如从C代码创建的)就是这样?

现在,事实证明,C编译器具有某种魔力,可以否认公共实例字段的存在(但请参见下文),但在PowerShell中,您可以这样做:

1
2
3
4
5
6
prompt> $d = [DayOfWeek]::Thursday
prompt> $d
Thursday
prompt> $d.value__ = 6
prompt> $d
Saturday

字段value__可以写入。

现在,为了在C中实现这一点,我不得不使用dynamic,因为在正常的编译时成员绑定中,C假装public实例字段不存在。当然,要使用dynamic,我们必须使用枚举值的装箱。

下面是C代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// create a single box for all of this example
Enum box = DayOfWeek.Thursday;

// add box to a hash set
var hs = new HashSet<Enum> { box, };

// make a dynamic reference to the same box
dynamic boxDyn = box;

// see and modify the public instance field
Console.WriteLine(boxDyn.value__);  // 4
boxDyn.value__ = 6;
Console.WriteLine(boxDyn.value__);  // 6 now

// write out box
Console.WriteLine(box);  // Saturday, not Thursday

// see if box can be found inside our hash set
Console.WriteLine(hs.Contains(box));  // False

// we know box is in there
Console.WriteLine(object.ReferenceEquals(hs.Single(), box));  // True

我认为这些评论本身就说明了问题。我们可以通过public字段改变枚举类型DayOfWeek的实例(可以是来自bcl程序集或"自制"程序集的任何枚举类型)。由于实例在哈希表中,且突变导致哈希代码发生变化,因此该实例在突变后处于错误的"bucket"中,HashSet<>无法运行。

为什么.NET的设计者选择将枚举类型的实例字段设置为public


让我试着为那些不熟悉如何在幕后生成枚举的读者理解这个令人困惑的问题。C代码:

1
enum E { A, B }

成为IL

1
2
3
4
5
6
.class private auto ansi sealed E extends [mscorlib]System.Enum
{
  .field public specialname rtspecialname int32 value__
  .field public static literal valuetype E A = int32(0x00000000)
  .field public static literal valuetype E B = int32(0x00000001)
}

或者,为了在C中重写它,枚举等价于以下伪C:

1
2
3
4
5
6
struct E : System.Enum
{
    public int value__;
    public const E A = 0;
    public const E B = 1;
}

问题是:为什么魔域value__是公开的?

我不想做这个设计决定,所以我只能做一个有根据的猜测。我有根据的猜测是:如果字段不是公共的,如何初始化结构的实例?

你做了一个构造器,然后你必须调用它,这就给了你一个抖动的工作,这项工作的性能代价是什么?如果答案是"它给我买了运行时,阻止我自己做一些愚蠢和危险的事情,我不应该在第一时间做,必须真正努力去做",那么我向你提交,这不是一个令人信服的成本效益比。

Since the instance was in a hashtable and the mutation lead to a change of hash code, the instance is in the wrong"bucket" after the mutation, and the HashSet cannot function.

这是几英里过去的"如果它伤害,当你这样做,然后停止这样做"行。