TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.76K subscribers
Post #2198 2.47K
День 1819. #ЗаметкиНаПолях
Непостоянные Строковые Константы
Ключевое слово const работает следующим образом: компилятор помещает строку в блок памяти только для чтения, а затем заменяет все ссылки на строку адресом строки в памяти. Поэтому следующий код:
const string foo = "bar";
Console.WriteLine(foo);
Console.WriteLine(foo);

Будет преобразован в:
Console.WriteLine("bar");
Console.WriteLine("bar");

В скомпилированном коде нет foo. Поэтому нельзя использовать в константах переменные или вещи, которые «неизвестны» во время компиляции. Но можем ли мы изменить константную строку?

Интернирование строк
Где-то в памяти должна храниться строка. Используемый здесь механизм называется интернированием строк. Компилятор проверяет все константные строки в коде и помещает их в специальную таблицу в памяти. Если у вас есть две константные строки с одинаковым значением, они будут помещены в одну запись таблицы. Поэтому код:
const string foo = "bar";
const string other_foo = "bar";

Console.WriteLine(foo);
Console.WriteLine(other_foo);

Console.WriteLine(ReferenceEquals(foo, other_foo));

Будет преобразован в:
Console.WriteLine("bar");
Console.WriteLine("bar");

Console.WriteLine((object)"bar" == "bar");

… и выведет True.

Компилятор заменит все ссылки на foo и other_foo строкой «bar», а поскольку строки интернированы, они будут равны.

Теперь попробуем изменить запись в таблице интернированных строк. Итак, по логике, даже после вызова ModifyString, на консоль должно быть выведено bar:
const string foo = "bar";
ModifyString(foo);
Console.WriteLine(foo);


Как это изменить? Просто получим адрес строки в памяти и изменим то, что там хранится:
unsafe void ModifyString(string foo)
{
fixed(char* f = foo)
{
f[0] = 'F';
f[1] = 'o';
f[2] = 'o';
f[3] = '!';
}
}

В итоге в выводе получим Foo. Мы не можем изменить строку (в смысле ее адреса), но можем изменить её содержимое. Интересно, что это также сработает и для переменной var foo = "bar".

Самые внимательные заметили, что мы записали больше символов, чем длина строки. Компилятор поместит строку в буфер с длиной строки + 1. Поэтому, если мы напишем больше, чем длина строки, мы перезапишем следующую строку в буфере. Выводится всё равно Foo, поскольку метаданные объекта сохраняют его длину, поэтому длина нашей строки по-прежнему составляет 3 символа. Вы можете подумать: а что, если в следующем блоке памяти есть что-нибудь, могу ли я там что-то перезаписать? Короткий ответ: нет, вы получите исключение System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt. (Попытка чтения или записи защищенной памяти». Часто это указывает на то, что другая память повреждена.)

Напоследок ещё один момент, касающийся интернирования строк. Если в конец предыдущего примера мы добавим вывод строки, построенной следующим образом:
Console.WriteLine(string.Concat("b", "ar"));

Как вы думаете, что будет выведено на консоль: Foo или bar?
Будет выведено bar, поскольку «динамические» строки не интернируются, и поэтому в нашем случае строка, созданная из string.Concat, находится по другому адресу в памяти.

Итого
Мы увидели, что константные строки в конце концов не такие уж и константные (не без костылей, но всё же). Вам, скорее всего (надеюсь), это не пригодится, но понимание внутренностей может быть интересным и полезным! Вот ссылка на Sharplab.io, если вы хотите напрямую поиграть с кодом.

Источник: https://steven-giesel.com/blogPost/544c33da-be2e-4d96-99c9-9313f48548bd/const-strings-are-not-so-const-after-all
  • 👍 27
  • 👎 1
More from @netdeveloperdiary
  1. Oct 8, 2026День 2808. #Карьера 5 Навыков, Которые Помогут Быстрее Стать Сеньором. Начало В ИТ есть се…
  2. Oct 7, 2026День 2807. #ЗаметкиНаПолях Типы Коллекций в .NET, Которые Стоит Попробовать. Окончание Нач…
  3. Oct 6, 2026🦈 Открытое собеседование на Middle C# | 6 октября, 19:00 МСК Приглашаем на открытое собес…
  4. Oct 6, 2026День 2806. #ЗаметкиНаПолях Типы Коллекций в .NET, Которые Стоит Попробовать. Начало Больши…
  5. Oct 5, 2026День 2805. #ЧтоНовенького #NET11 Аргументы в Выражениях Коллекций в C#15 В C#15 реализован…
  6. Oct 4, 2026День 2804. #ВопросыНаСобеседовании Марк Прайс предложил свой набор из 60 вопросов (как тех…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →