Типы .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 не может функционировать.
В нескольких милях от "если больно, когда вы это делаете, тогда прекратите выполнение этой" линии.