Looking, with reflection, at the fields of an enum type, I noticed to my surprise that the "backing" instance field that holds the actual value of a particular instance of the enum is not private
, as I would have thought, but public
. And it was not readonly
either. (IsPublic
true, IsInitOnly
false.)
Many people consider "mutable" value types in the .NET type system "evil", so why are the enum types (as created from C# code for example) just that?
Now, as it turns out, the C# compiler has some kind of magic that denies the existence of the public instance field (but see below), but in e.g. PowerShell you can do this:
prompt> $d = [DayOfWeek]::Thursday
prompt> $d
Thursday
prompt> $d.value__ = 6
prompt> $d
Saturday
The field value__
can be written to.
Now, to do this in C#, I had to use dynamic
because it seems that with the normal compile-time member binding, C# pretends that the public
instance field does not exist. Of course to use dynamic
, we will have to use boxing of the enum value.
Here's a C# code example:
// 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
I think the comments speak for themselves. We can mutate an instance of the enum type DayOfWeek
(could be any enum type from a BCL assembly or from a "home-made" assembly) through a public
field. 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.
Why did the designers of .NET choose to make the instance field of enum types public
?
enums should be immutable (in fact, unless you introduce some mutable state in them, they already are), enums cannot be extended by other types : once defined, their set of values is closed and cannot be modified.
All built-in value types like int, double, etc., are mutable types and can be made immutable by adding the modifier "readonly" before the variables. If a mutable reference type is specified with a readonly modifier, the C# compiler generates a warning.
C# enum is a value type with a set of related named constants often referred as an enumerator list. The C# enum keyword is used to declare an enumeration. It is a primitive data type, which is user-defined.
An enum type is a distinct value type (§8.3) that declares a set of named constants. declares an enum type named Color with members Red , Green , and Blue .
Let me try to make sense of this rather confusing question for readers who are unfamiliar with how enums are generated behind the scenes. The C# code:
enum E { A, B }
becomes the IL
.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)
}
Or, to rewrite that in C# again, the enum is equivalent to the following pseudo-C#:
struct E : System.Enum
{
public int value__;
public const E A = 0;
public const E B = 1;
}
The question is: why is the magical field value__
public?
I wasn't around for this design decision, so I'd have to make an educated guess. My educated guess would be: how do you initialize an instance of the struct if the field is not public?
You make a constructor, which you then have to call, and that is giving work to the jitter, and what does the performance cost of that work buy you? If the answer is "it buys me the runtime preventing myself from doing something foolish and dangerous that I shouldn't be doing in the first place and had to work really hard to do at all" then I submit to you that this is not a compelling cost-to-benefit ratio.
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.
That's several miles past the "if it hurts when you do that then stop doing that" line.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With