Типы .NET перечислены на самом деле изменяемые типы значений?

Глядя с отражением в поля типа перечисления, я с удивлением заметил, что поле экземпляра "backing", которое содержит фактическое значение конкретного экземпляра перечисления, не является private, так как я бы думал, но public. И это было не так. (IsPublic true, IsInitOnly false.)

Многие люди считают "изменчивые" типы значений в системе типа .NET "злыми", поэтому почему типы перечисления (например, созданные из кода С#) только это?

Теперь, как оказалось, у компилятора С# есть какая-то магия, которая отрицает существование поля публичного экземпляра (но см. ниже), но, например, PowerShell вы можете сделать это:

prompt> $d = [DayOfWeek]::Thursday
prompt> $d
Thursday
prompt> $d.value__ = 6
prompt> $d
Saturday

Поле value__ можно записать в.

Теперь, чтобы сделать это в С#, мне пришлось использовать dynamic, потому что кажется, что при нормальной привязке элемента компиляции С# делает вид, что поле экземпляра public не существует. Конечно, чтобы использовать dynamic, нам нужно будет использовать бокс для значения перечисления.

Здесь пример кода С#:

// 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

Я думаю, что комментарии говорят сами за себя. Мы можем мутировать экземпляр типа перечисления DayOfWeek (может быть любым типом перечисления из сборки BCL или из сборки "домашнего" ) через поле public. Поскольку экземпляр был в хэш-таблице, и мутация привела к изменению хеш-кода, экземпляр находится в неправильном "ведре" после мутации, а HashSet<> не может функционировать.

Почему разработчики .NET решили создать поле экземпляра типов перечисления public?

Ответы

Ответ 1

Позвольте мне попытаться понять этот довольно запутанный вопрос для читателей, которые не знакомы с тем, как переписчики создаются за кулисами. Код С#:

enum E { A, B }

становится 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)
} 

Или, чтобы переписать это в С# снова, перечисление эквивалентно следующему псевдо-С#:

struct E : System.Enum
{
    public int value__;
    public const E A = 0;
    public const E B = 1;
}

Вопрос: почему магическое поле value__ public?

Меня не было вокруг этого дизайнерского решения, поэтому я должен был бы получить обоснованное предположение. Мое образованное предположение было бы следующим: как вы инициализируете экземпляр структуры, если поле не является общедоступным?

Вы создаете конструктор, который затем вам нужно вызвать, и это дает работу джиттеру, и какова стоимость выкупа этой работы? Если ответ заключается в том, что "он покупает мне время выполнения, препятствуя мне делать что-то глупое и опасное, что я не должен делать в первую очередь и должен был работать очень тяжело вообще", тогда я подчиняюсь вам, что это не привлекательное соотношение затрат и выгод.

Поскольку экземпляр был в хэш-таблице, и мутация привела к изменению хэш-кода, экземпляр находится в неправильном "ведре" после мутации, и HashSet не может функционировать.

В нескольких милях от "если больно, когда вы это делаете, тогда прекратите выполнение этой" линии.