Урок по треку C# и .NET 6 мин

Строки в C#

Неизменяемость, интернирование, StringBuilder, сравнение и культуры, интерполяция.

string в .NET это ссылочный тип с поведением значения: он неизменяемый, а == сравнивает содержимое. Отсюда большинство вопросов на собесе.

Неизменяемость

Любая «модификация» создаёт новую строку:

string s = "hello";
s.ToUpper();          // результат выброшен, s не изменилась
s = s.ToUpper();      // теперь s = "HELLO"

Плюсы: строку безопасно передавать между потоками, хранить как ключ словаря (хеш не меняется), разделять одинаковые литералы. Минус: склейка в цикле.

Конкатенация и StringBuilder

string result = "";
foreach (var part in parts) result += part;   // O(n²) по копированию

Каждый += создаёт строку длины «всё накопленное плюс кусок». Для неизвестного числа частей берите StringBuilder или string.Join/string.Concat. А выражение a + b + c в одной строке компилятор сам превращает в один вызов string.Concat, там StringBuilder не нужен.

Интерполяция $"Привет, {name}" с C# 10 компилируется в DefaultInterpolatedStringHandler: значения добавляются без упаковки, буфер берётся из пула.

Сравнение

  • == и Equals у строк сравнивают содержимое порядково.
  • Если статический тип object, == сравнивает ссылки.
  • Регистр и культуру задают явно:
string.Equals(a, b, StringComparison.OrdinalIgnoreCase); // идентификаторы
string.Compare(a, b, StringComparison.CurrentCulture);   // сортировка текста для людей
var dict = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);

a.ToLower() == b.ToLower() хуже: две лишние строки и зависимость от культуры (знаменитая турецкая I).

Интернирование

Строковые литералы в сборке хранятся в пуле: два одинаковых литерала это один объект. Строки, созданные во время выполнения, в пул не попадают, пока не вызвать string.Intern. Для собеса важно понимать это, а в коде сравнивать по содержимому.

Пустые строки и null

string.IsNullOrEmpty(s) ловит null и "", string.IsNullOrWhiteSpace(s) ещё и строку из пробелов.

Юникод

char это UTF-16 code unit. Эмодзи и редкие иероглифы занимают два char (суррогатная пара), поэтому "👍".Length == 2. Для «видимых символов» есть StringInfo и Rune.

Строки без аллокаций

ReadOnlySpan<char> даёт окно на часть строки без копирования:

ReadOnlySpan<char> line = "id=42;name=Ira";
int eq = line.IndexOf('=');
int id = int.Parse(line.Slice(eq + 1, 2));   // без Substring

Парсеры чисел, IndexOf, Trim, StartsWith принимают span. В горячем парсинге это убирает основную часть мусора.

Частые ошибки

  • Вызывать Replace, Trim, ToUpper и не использовать результат.
  • Склеивать строки в цикле через +=.
  • Сравнивать без StringComparison и получать разные результаты на разных серверах.
  • Форматировать числа без CultureInfo.InvariantCulture для машинного вывода: на русской локали будет запятая.

Что спросят на собесе

  • Почему строки неизменяемы и что из этого следует?
  • Когда нужен StringBuilder, а когда нет?
  • Чем == отличается от Equals и ReferenceEquals для строк?
  • Что такое интернирование?
  • Как правильно сравнить строки без учёта регистра?

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.