【发布时间】:2013-07-26 21:24:48
【问题描述】:
通过反射查看枚举类型的字段,我惊讶地注意到保存枚举的特定实例的实际值的“支持”实例字段不是private,就像我应该做的那样想,但是public。它也不是readonly。 (IsPublic 真,IsInitOnly 假。)
很多人认为 .NET 类型系统中的“可变”值类型是“邪恶的”,那么为什么是枚举类型(例如从 C# 代码创建)就是这样?
现在,事实证明,C# 编译器有某种魔法可以否认公共实例字段的存在(但见下文),但在例如PowerShell 你可以这样做:
prompt> $d = [DayOfWeek]::Thursday
prompt> $d
Thursday
prompt> $d.value__ = 6
prompt> $d
Saturday
value__ 字段可以写入。
现在,要在 C# 中执行此操作,我必须使用 dynamic,因为似乎使用正常的编译时成员绑定,C# 会假装 public 实例字段不存在。当然要使用dynamic,我们将不得不使用枚举值的装箱。
这是一个 C# 代码示例:
// 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
我认为 cmets 不言自明。我们可以通过public 字段改变枚举类型DayOfWeek 的实例(可以是来自BCL 程序集或“自制”程序集的任何枚举类型)。由于实例在hashtable中,mutation导致hash码发生变化,导致实例在mutation后在错误的“bucket”中,HashSet<>无法运行。
.NET的设计者为什么选择将枚举类型的实例字段设为public?
【问题讨论】:
-
我想知道您对理想实现的想法。你愿意分享吗?
-
其实有一种更简单的方法可以证明枚举类型是可变的,无需装箱或动态:gist.github.com/thomaslevesque/6100447
-
@KenKin 好吧,最重要的是,我的想法是保存当前实例实际值的实例字段应该是
private。在我看来,它的名称是否包含“有趣”字符(C# 中的标识符名称中不可能出现的字符)并不重要。 -
@ThomasLevesque 有了它,您甚至可以“变异”真正不可变的类型,如
DateTime(更改私有字段ulong dateData)甚至所谓的原始类型,如int(更改私有字段 @ 987654341@)。您需要做的唯一更改是在GetField调用中指定BindingFlags.Instance | BindingFlags.NonPublic。但我不认为你可以通过反射改变任何东西是一个问题。这就是反思的本质。 (尽管__makerefC# 关键字很有趣,以避免按值复制装箱。)我上面给出的示例仅因为value__是公开的。 -
@JeppeStigNielsen,说得好。我忘了反射可以让你改变只读字段...
标签: c# .net powershell enums immutability