Укороченная версия:
Код С#
typeof(string).GetField("Empty").SetValue(null, "Hello world!");
Console.WriteLine(string.Empty);
при компиляции и запуске выдает вывод "Hello world!"
в .NET версии 4.0 и более ранних, но ""
под .NET 4.5 и .NET 4.5.1.
Как можно игнорировать запись в поле или кто сбрасывает это поле?
Более длинная версия:
Я никогда не понимал, почему string.Empty
поле (также известный как [mscorlib]System.String::Empty
) не const
(ака. literal
), см " Почему не String.Empty постоянная? ". Это означает, что, например, в С# мы не можем использовать string.Empty
в следующих ситуациях:
- В операторе
switch
в формеcase string.Empty:
- В качестве значения по умолчанию для необязательного параметра, например
void M(string x = string.Empty) { }
- При применении атрибута, например
[SomeAttribute(string.Empty)]
- Другие ситуации, когда требуется постоянная времени компиляции
что имеет значение для известной "религиозной войны" по поводу того, использовать ли string.Empty
или ""
, см. " В С# мне следует использовать string.Empty или String.Empty или" "для инициализации строки? ".
Пару лет назад я позабавился, установив Empty
для какого-то другого экземпляра строки с помощью отражения, и увидел, как много частей BCL начали вести себя странно из-за этого. Это было довольно много. И изменение Empty
ссылки, казалось, сохранялось в течение всего срока службы приложения. Сейчас, на днях, я попытался повторить этот маленький трюк, но потом использовал машину .NET 4.5, и я больше не мог этого делать.
(NB! Если на вашем компьютере установлен .NET 4.5, возможно, ваш PowerShell
все еще использует более старую версию .NET(EDIT: верно только для Windows 7 или более ранней версии, где PowerShell не обновлялся после PowerShell 2.0), поэтому попробуйте вставить копию [String].GetField("Empty").SetValue($null, "Hello world!")
PowerShell, чтобы увидеть некоторые эффекты изменения этой ссылки.)
Когда я попытался найти причину для этого, я наткнулся на интересную тему "В чем причина этой ошибки FatalExecutionEngineError в .NET 4.5 beta? ". В принятом ответе на этот вопрос отмечается, что в версии 4.0 System.String
имел статический конструктор .cctor
в котором было установлено поле Empty
(в источнике С# это, конечно, просто инициализатор поля, конечно) в то время как в 4.5 нет статического конструктора. В обеих версиях само поле выглядит одинаково:
.field public static initonly string Empty
(как видно с IL DASM).
Похоже, что нет никаких других полей, кроме String::Empty
. В качестве примера я экспериментировал с System.Diagnostics.Debugger::DefaultCategory
. Этот случай кажется аналогичным: запечатанный класс, содержащий static readonly
поле только для static initonly
(static initonly
) типа string
. Но в этом случае работает нормально, чтобы изменить значение (ссылку) через отражение.
Вернуться к вопросу:
Как технически возможно, что Empty
не меняется (в 4.5), когда я устанавливаю поле? Я проверил, что компилятор С# не "обманывает" чтение, он выводит IL как:
ldsfld string [mscorlib]System.String::Empty
поэтому фактическое поле должно быть прочитано.
Редактировать после Баунти был поставлен на мой вопрос: Обратите внимание, что операция записи (которая нуждается отражение точно, так как поле readonly
для initonly
readonly
(ака initonly
в IL)) на самом деле работает, как ожидалось. Это операция чтения, которая является аномальной. Если вы читаете с отражением, как в typeof(string).GetField("Empty").GetValue(null)
, все в норме (т.е. видно изменение значения). Смотрите комментарии ниже.
Итак, лучший вопрос: почему эта новая версия фреймворка обманывает, когда читает это конкретное поле?