Ответ
Строки в 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> или вообще массивы символов. А иначе получишь по первое число, причём в проде, в три часа ночи.