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

Ответ

Строки в C# — это неизменяемые (immutable) последовательности символов Unicode, представленные типом System.String. Их ключевые особенности:

1. Неизменяемость (Immutability) При любой модификации создаётся новый объект строки. Это обеспечивает потокобезопасность и безопасность, но требует аккуратной работы с производительностью.

string original = "Hello";
string modified = original + " World"; // Создаётся новая строка, "Hello" остаётся в памяти
Console.WriteLine(object.ReferenceEquals(original, modified)); // False

2. Интернирование строк (String Interning) CLR хранит литералы в пуле строк (intern pool), что позволяет повторно использовать одинаковые строковые литералы для экономии памяти.

string a = "test";
string b = "test";
string c = new StringBuilder().Append("te").Append("st").ToString();
Console.WriteLine(object.ReferenceEquals(a, b)); // True (интернирование)
Console.WriteLine(object.ReferenceEquals(a, c)); // False (не интернирована)
Console.WriteLine(string.IsInterned(c) != null); // Можно принудительно интернировать

3. Оптимизация конкатенации Для множественных операций конкатенации всегда используйте StringBuilder вместо оператора + или string.Concat() в цикле.

// Плохо: создаёт промежуточные строки O(n²)
string result = "";
for (int i = 0; i < 100; i++) result += i;

// Хорошо: эффективное построение
var sb = new StringBuilder();
for (int i = 0; i < 100; i++) sb.Append(i);
string result = sb.ToString();

4. Форматирование Используйте интерполяцию строк ($"Value: {value}") вместо string.Format() для лучшей читаемости и производительности (компилятор оптимизирует в string.Format).

5. Юникод и суррогатные пары Один символ (char) может занимать 2 байта (UTF-16), а для символов за пределами Basic Multilingual Plane (например, эмодзи) требуется суррогатная пара — два char.

string emoji = "😀";
Console.WriteLine(emoji.Length); // 2 (суррогатная пара)
Console.WriteLine(new StringInfo(emoji).LengthInTextElements); // 1 (графема)

6. Сравнение строк Используйте StringComparison для явного указания правил сравнения, особенно в культурно-зависимых сценариях.

bool equal = string.Equals("test", "TEST", StringComparison.OrdinalIgnoreCase);

Практическое правило: При активной работе со строками (парсинг, построение) всегда оценивайте необходимость использования StringBuilder, ReadOnlySpan<char> (для .NET Core) или массивов символов.

Ответ 18+ 🔞

А, ну вот, опять про строки в C# рассказывать, да? Ну ладно, слушай сюда, раз уж начал.

Смотри, строки в C# — это как тот самый бабушкин сервиз, который стоит за стеклом. Посмотреть можно, потрогать — вроде тоже, но если попробуешь нацарапать на нём гвоздём своё имя — бабушка тебе новую жопу нарисует, а тарелка останется как была. Это и есть их главная фишка — неизменяемость (immutability). Любая попытка что-то поменять создаёт новую строку, а старая так и болтается в памяти, пока сборщик мусора не придёт и не вынесет её мозги.

string original = "Привет";
string modified = original + " мир"; // Здесь родилась новая строка, а старая "Привет" теперь сидит в углу и плачет
Console.WriteLine(object.ReferenceEquals(original, modified)); // False — это два разных объекта, Карл!

Дальше — интернирование строк. Это такая хитрая магия CLR, которая говорит: "А зачем нам десять одинаковых строк 'Hello' в памяти, если можно сделать одну и все на неё ссылаться?". С литералами это работает на раз.

string a = "test";
string b = "test";
string c = new StringBuilder().Append("te").Append("st").ToString(); // А вот эта уже не из пула
Console.WriteLine(object.ReferenceEquals(a, b)); // True — это одна и та же строка, они как близнецы-братья
Console.WriteLine(object.ReferenceEquals(a, c)); // False — а это уже левый парень с района

Теперь про конкатенацию, тут вообще отдельная песня. Если ты делаешь + в цикле — ты просто конченый идиот, прости за прямоту. Каждый + — это новая строка, старые — на помойку, производительность летит в пизду квадратично.

// Делать так — это прям просить, чтобы тебя уволили
string result = "";
for (int i = 0; i < 10000; i++) result += i; // Овердохуища мусора и тормозов

// А вот так — уже с мозгами
var sb = new StringBuilder();
for (int i = 0; i < 10000; i++) sb.Append(i);
string result = sb.ToString(); // Всё чисто, быстро, красиво

Форматирование — тут вообще сказка. Раньше писали string.Format("Значение: {0}", value), сейчас можно просто $"Значение: {value}". Компилятор сам всё оптимизирует, читается в разы лучше. Кто так не делает — тот, блядь, ретроград.

Про юникод вообще молчу. Думаешь, один символ — это один char? А вот хуй там! Возьми эмодзи "😀" — это же суррогатная пара, два char в одном флаконе.

string emoji = "😀";
Console.WriteLine(emoji.Length); // 2 — вот тебе и сюрприз
Console.WriteLine(new StringInfo(emoji).LengthInTextElements); // 1 — а вот так уже правильно, одна графема

И на последок — сравнение строк. Никогда, слышишь, никогда не используй просто == для строк, если не уверен на все сто, что тебе нужно. Указывай StringComparison явно, иначе однажды твоё приложение на сервере с турецкой локалью просто сломается, и ты будешь неделю искать, почему 'i' и 'I' не равны.

bool equal = string.Equals("test", "TEST", StringComparison.OrdinalIgnoreCase); // Вот так — правильно

Итог: со строками нужно обращаться как с хрустальной вазой — аккуратно, с пониманием, что внутри. Если нужно много резать, клеить, менять — бери StringBuilder, Span<char> или вообще массивы символов. А иначе получишь по первое число, причём в проде, в три часа ночи.