Что такое интернирование строк в C#?

Ответ

Интернирование строк — это механизм CLR, который хранит в памяти (string intern pool) только одну уникальную копию каждого строкового литерала. Это оптимизация для экономии памяти.

Как работает:

  1. Все строковые литералы в коде автоматически интернируются на этапе компиляции/JIT.
  2. При создании строки CLR сначала проверяет пул. Если строка там есть, возвращается ссылка на существующий объект.

Пример, демонстрирующий разницу:

string literal1 = "Hello";
string literal2 = "Hello"; // Тот же литерал
string dynamicStr = new StringBuilder().Append("He").Append("llo").ToString(); // Создана динамически

// Сравнение ссылок (ReferenceEquals)
Console.WriteLine(object.ReferenceEquals(literal1, literal2)); // True: оба ссылаются на один объект в пуле
Console.WriteLine(object.ReferenceEquals(literal1, dynamicStr)); // False: dynamicStr — новый объект в куче

// Принудительное интернирование динамической строки
string internedDynamic = string.Intern(dynamicStr);
Console.WriteLine(object.ReferenceEquals(literal1, internedDynamic)); // True: теперь ссылка на объект из пула

// Проверка на интернированность
Console.WriteLine(string.IsInterned("Hello") != null); // True
Console.WriteLine(string.IsInterned(dynamicStr) != null); // False (до вызова string.Intern)

Практические соображения:

  • Плюс: Экономит память при множественном использовании одинаковых литералов.
  • Минус: Пул живет до выгрузки домена приложения. Динамическое интернирование большого числа уникальных строк может увеличить потребление памяти без возможности сборки мусора.
  • Использовать string.Intern вручную стоит только для часто повторяющихся строк, создаваемых динамически (например, ключей словаря).

Ответ 18+ 🔞

Слушай, вот есть у нас в C# такая фишка — интернирование строк. Представь, что CLR — это такой занудный библиотекарь, который ненавидит, когда у него в хранилище лежат пять одинаковых книг. Он берёт все строковые литералы из твоего кода и кладёт в специальную полку — string intern pool. На каждую уникальную строку — один экземпляр. И всё.

Как это, блядь, работает:

  1. Все строки, которые ты в коде прямо написал в кавычках, этот библиотекарь автоматом засовывает на ту самую полку при компиляции.
  2. Когда ты пытаешься создать новую строку, он сначала лезет на полку смотреть: а нет ли уже такой? Если есть — просто тычет пальцем в старую копию. Зачем плодить сущности, верно?

*Смотри, вот наглядная разница, чтобы не ебть мозг:**

string literal1 = "Hello";
string literal2 = "Hello"; // Это тот же самый литерал, сука!
string dynamicStr = new StringBuilder().Append("He").Append("llo").ToString(); // А вот это уже собрали на коленке, динамически

// Сравниваем не по буквам, а по адресу в памяти (ReferenceEquals)
Console.WriteLine(object.ReferenceEquals(literal1, literal2)); // True: оба тыкают в один и тот же объект с полки
Console.WriteLine(object.ReferenceEquals(literal1, dynamicStr)); // False: dynamicStr — это новый, отдельный объект в куче, его на полку не клали

// Но можно и динамическую строку затолкать на полку принудительно!
string internedDynamic = string.Intern(dynamicStr);
Console.WriteLine(object.ReferenceEquals(literal1, internedDynamic)); // True: теперь и она ссылается на тот же старый объект с полки

// А можно спросить: "Эй, библиотекарь, а эта строка у тебя на полке лежит?"
Console.WriteLine(string.IsInterned("Hello") != null); // True, конечно лежит
Console.WriteLine(string.IsInterned(dynamicStr) != null); // False (до вызова string.Intern), потому что её туда не клали

А теперь, блядь, практика:

  • Плюс: Памяти жрёт меньше, если одни и те же строки по коду болтаются. Экономия, епта!
  • Минус: Полка-то эта живёт, пока приложение не вырубишь. Если ты начнёшь вручную через string.Intern закидывать туда миллион разных, уникальных строк — они там навечно останутся и сборщик мусора до них не доберётся. Получишь утечку памяти, вот тебе и оптимизация.
  • Так когда это руками использовать? Да только если у тебя есть динамически создаваемые строки, которые до охуения часто повторяются. Ну, например, ключи в каком-нибудь глобальном словаре, по которому все постоянно бьют. Тогда есть смысл их заинтернировать, чтобы не плодить. В остальных случаях — не ебись, система сама разберётся.