Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Are .NET enum types actually mutable value types?

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?

like image 290
Jeppe Stig Nielsen Avatar asked Jul 26 '13 21:07

Jeppe Stig Nielsen


People also ask

Are enums mutable C#?

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.

Are value types mutable C#?

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.

Is enum a value type in C#?

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.

Is an enum a value type?

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 .


1 Answers

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.

like image 149
Eric Lippert Avatar answered Sep 28 '22 02:09

Eric Lippert