Основы C#

Строки в C#: основные методы String

Обновлено: · 6 мин чтения

Строка в C# - это объект типа string, то есть последовательность символов Unicode, и почти любая программа работает с ней постоянно. Ниже собраны методы класса String, которые нужны чаще всего: поиск, замена, разбиение, обрезка, форматирование, а также причины, по которым строки неизменяемы и когда стоит взять StringBuilder.

Строки в C#: основные методы String
Содержание
  1. Что такое строка и как её создать
  2. Неизменяемость строк
  3. Поиск в строке: IndexOf, Contains, StartsWith
  4. Substring, Remove, Insert и Replace
  5. Split и Join: разбиение и склейка
  6. Trim, регистр и выравнивание
  7. Конкатенация, интерполяция и форматирование
  8. Когда нужен StringBuilder
  9. Типичные ошибки при работе со строками
  10. Частые вопросы

Что такое строка и как её создать

Тип string - это псевдоним класса System.String. Строка хранит символы в кодировке UTF-16, а длину можно узнать через свойство Length. Литералы пишутся в двойных кавычках, а пустую строку лучше задавать через string.Empty или просто "".

string name = "Анна";
string empty = string.Empty;
string path = @"C:\Projects\App";      // буквальная строка, слэши не нужно удваивать
string multi = "Первая строка\nВторая строка";
string raw = """
    Многострочный "сырой" литерал
    без экранирования кавычек
    """;

Console.WriteLine(name.Length);   // 4
Console.WriteLine(name[0]);       // А

К отдельному символу строки обращаются по индексу, как к элементу массива, но менять его нельзя: индексатор доступен только для чтения. Если нужно пройти по всем символам, подойдёт цикл foreach, ведь string реализует перечисление символов типа char.

Отдельно стоит помнить про null и пустую строку. Это разные значения: null означает отсутствие объекта, а пустая строка - объект с нулевой длиной. Для проверки обоих случаев сразу есть методы string.IsNullOrEmpty и string.IsNullOrWhiteSpace, и именно их стоит использовать при проверке ввода пользователя.

string input = "   ";
Console.WriteLine(string.IsNullOrEmpty(input));       // False
Console.WriteLine(string.IsNullOrWhiteSpace(input));  // True

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

Строки в .NET неизменяемы (immutable): после создания содержимое объекта уже не меняется. Любой метод, который будто бы изменяет строку, на самом деле возвращает новый объект, а старый остаётся прежним. Самая частая ошибка новичков - вызвать метод и не присвоить результат.

string s = "  hello  ";
s.Trim();                 // результат потерян, s не изменилась
Console.WriteLine(s);     // "  hello  "

s = s.Trim();             // правильно
Console.WriteLine(s);     // "hello"

У неизменяемости есть плюсы: строку безопасно передавать между потоками, её можно использовать как ключ словаря, а одинаковые литералы хранятся в пуле строк и занимают память один раз. Минус тоже очевиден: если собирать большой текст через многократное сложение, каждый раз создаётся новый объект, и нагрузка на память растёт.

Поиск в строке: IndexOf, Contains, StartsWith

Для проверки наличия подстроки используют Contains, StartsWith и EndsWith. Они возвращают bool. Если нужна позиция, то подойдут IndexOf и LastIndexOf: они возвращают индекс первого или последнего вхождения, а если ничего не найдено, то -1.

string text = "Привет, мир! Привет, C#!";

Console.WriteLine(text.Contains("мир"));          // True
Console.WriteLine(text.StartsWith("Привет"));     // True
Console.WriteLine(text.EndsWith("C#!"));          // True
Console.WriteLine(text.IndexOf("Привет"));        // 0
Console.WriteLine(text.LastIndexOf("Привет"));    // 13
Console.WriteLine(text.IndexOf("Java"));          // -1

По умолчанию часть этих методов учитывает регистр. Чтобы сравнить без учёта регистра, передайте StringComparison.OrdinalIgnoreCase. Этот вариант быстрый и не зависит от региональных настроек компьютера, поэтому его стоит выбирать для идентификаторов, расширений файлов и подобных данных.

string file = "Report.PDF";
bool isPdf = file.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase);
Console.WriteLine(isPdf);   // True

Для сравнения двух строк используйте string.Equals с нужным StringComparison или оператор ==, который сравнивает содержимое, а не ссылки. Метод string.Compare нужен, когда требуется порядок: он вернёт отрицательное число, ноль или положительное.

Substring, Remove, Insert и Replace

Метод Substring вырезает часть строки: первый аргумент - начальный индекс, второй (необязательный) - длина. Если выйти за границы, будет исключение ArgumentOutOfRangeException, поэтому индекс перед вызовом стоит проверять. Современная альтернатива - диапазоны: text[2..5].

string s = "Программирование";

Console.WriteLine(s.Substring(0, 7));   // Програм
Console.WriteLine(s.Substring(7));      // мирование
Console.WriteLine(s[..7]);              // Програм
Console.WriteLine(s[^5..]);             // ование

Remove удаляет символы с указанной позиции, Insert вставляет подстроку, а Replace заменяет все вхождения одной подстроки или символа другой. Все три метода возвращают новую строку.

string phone = "+7 (999) 123-45-67";
string digits = phone.Replace(" ", "").Replace("(", "").Replace(")", "").Replace("-", "");
Console.WriteLine(digits);   // +79991234567

string word = "программа";
Console.WriteLine(word.Insert(0, "под"));   // подпрограмма
Console.WriteLine(word.Remove(4));          // прог

Replace заменяет все совпадения сразу и не умеет делать замену по шаблону. Если нужно заменять по маске, например убрать все цифры, подключают регулярные выражения из пространства имён System.Text.RegularExpressions.

Split и Join: разбиение и склейка

Split делит строку по разделителю и возвращает массив строк. Разделителем может быть символ, строка или набор символов. Параметр StringSplitOptions.RemoveEmptyEntries выбрасывает пустые элементы, а TrimEntries дополнительно обрезает пробелы вокруг каждой части. Подробнее про обратное преобразование есть отдельный материал про перевод строки в массив.

string line = "яблоко, груша ,, слива,  вишня";
string[] fruits = line.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

foreach (string f in fruits)
    Console.WriteLine(f);
// яблоко
// груша
// слива
// вишня

string joined = string.Join(" | ", fruits);
Console.WriteLine(joined);   // яблоко | груша | слива | вишня

Обратная операция выполняется методом string.Join: он принимает разделитель и коллекцию и возвращает единую строку. Это быстрее и аккуратнее, чем собирать результат в цикле через плюс, и не оставляет лишнего разделителя в конце.

Trim, регистр и выравнивание

Trim убирает пробельные символы с обоих концов, TrimStart и TrimEnd - только с одного. Можно передать и список символов, которые нужно срезать. Для смены регистра есть ToUpper и ToLower, а для сравнения без регистра лучше брать ToUpperInvariant или сравнение через StringComparison.

string raw = "  ***Заголовок***  ";
Console.WriteLine(raw.Trim());              // ***Заголовок***
Console.WriteLine(raw.Trim().Trim('*'));    // Заголовок
Console.WriteLine("c# blog".ToUpper());     // C# BLOG

Console.WriteLine("42".PadLeft(6, '0'));    // 000042
Console.WriteLine("ID".PadRight(6, '.') + "|");   // ID....|

Методы PadLeft и PadRight дополняют строку до нужной длины, что удобно при выводе таблиц в консоль и при формировании номеров с ведущими нулями.

Конкатенация, интерполяция и форматирование

Склеивать строки можно оператором +, методом string.Concat, а в современном коде чаще используют интерполяцию: перед кавычками ставится знак доллара, а выражения пишутся в фигурных скобках. Интерполяция читается лучше, и компилятор сам превращает её в эффективный вызов.

string user = "Олег";
int age = 28;
double balance = 1523.456;

Console.WriteLine("Имя: " + user + ", возраст: " + age);
Console.WriteLine($"Имя: {user}, возраст: {age}");
Console.WriteLine($"Баланс: {balance:F2} руб.");      // Баланс: 1523,46 руб.
Console.WriteLine($"Всего: {balance,12:N1}|");         // выравнивание по ширине
Console.WriteLine($"Дата: {DateTime.Now:dd.MM.yyyy}");
Console.WriteLine(string.Format("{0} - {1:C}", user, 99.9m));

После двоеточия внутри скобок указывается формат: F2 даёт два знака после запятой, N1 - разделители тысяч, C - денежный формат. Число через запятую задаёт ширину поля, положительное выравнивает вправо, отрицательное - влево. Результат зависит от региональных настроек, поэтому для файлов и обмена данными используют CultureInfo.InvariantCulture.

using System.Globalization;

double price = 12.5;
string forFile = price.ToString("F2", CultureInfo.InvariantCulture);
Console.WriteLine(forFile);   // 12.50

Когда нужен StringBuilder

Если строка собирается в цикле из сотен или тысяч частей, обычное сложение создаёт множество промежуточных объектов. Класс StringBuilder из System.Text хранит изменяемый буфер, и добавление в него не копирует весь текст заново. В итоге вызывается ToString.

using System.Text;

var sb = new StringBuilder();
for (int i = 1; i <= 5; i++)
{
    sb.Append(i).Append(", ");
}
sb.Length -= 2;                 // убрать последнюю запятую с пробелом
sb.AppendLine().Append("конец");
Console.WriteLine(sb.ToString());

Для нескольких склеек подряд (три-пять операторов +) разницы вы не заметите, а вот при обработке больших файлов или построении длинного отчёта StringBuilder выигрывает по скорости и памяти. У него есть также методы Insert, Remove и Replace, работающие прямо в буфере.

Типичные ошибки при работе со строками

  • Забытое присваивание результата: s.Replace("a", "b") без s = ... ничего не меняет.
  • Сравнение с учётом регистра там, где пользователь может ввести любой вариант написания.
  • Вызов методов у строки со значением null, что приводит к NullReferenceException; помогает оператор ?. и проверка IsNullOrEmpty.
  • Сборка большого текста через + в цикле вместо StringBuilder или string.Join.
  • Выход за границы в Substring: перед вырезанием проверьте длину.
  • Зависимость от региональных настроек при выводе чисел и дат.

Исключения из-за неверных индексов можно перехватывать, как описано в материале про try-catch, но надёжнее просто проверять условия заранее.

Частые вопросы

Чем отличаются string и String?

Это одно и то же. Ключевое слово string является псевдонимом класса System.String, и в коде разницы нет. По принятому стилю для типа пишут string, а для вызова статических методов часто String или string, например string.Join.

Почему нельзя изменить символ строки по индексу?

Потому что строки неизменяемы. Чтобы заменить один символ, создайте новую строку через Replace, Substring или переведите текст в массив char, измените нужный элемент и соберите строку обратно через new string(array).

Как сравнить строки без учёта регистра?

Используйте string.Equals(a, b, StringComparison.OrdinalIgnoreCase). Не стоит приводить обе строки к нижнему регистру ради сравнения: это создаёт лишние объекты и может давать неверный результат для некоторых языков.

Что быстрее: плюс или StringBuilder?

Для двух-трёх склеек разницы практически нет, и интерполяция удобнее. В цикле с большим числом шагов StringBuilder заметно быстрее, так как не копирует текст на каждой итерации.

Как посчитать количество вхождений подстроки?

Готового метода нет. Можно в цикле вызывать IndexOf с позиции, следующей за предыдущим найденным вхождением, либо применить Regex.Matches и взять его Count. Для одного символа подойдёт text.Count(c => c == 'a') из LINQ.

Чем пустая строка отличается от null?

Пустая строка - это существующий объект без символов, её длина равна нулю. Значение null означает, что объекта нет, и любой вызов метода у него приведёт к исключению. Для проверки обоих вариантов используйте string.IsNullOrEmpty.