Menu

Substring в C#: как безопасно получить часть строки

Как работает String.Substring в C#: аргументы начального индекса и длины, ArgumentOutOfRangeException и как его избежать, извлечение текста вокруг результатов IndexOf, первые и последние n символов и оператор диапазона из C# 8.

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

Substring возвращает новую строку, составленную из части существующей. У него две перегрузки:

  • s.Substring(startIndex): от startIndex до конца.
  • s.Substring(startIndex, length): length символов, начиная с startIndex.

Индексы начинаются с 0, а второй аргумент это длина, а не конечная позиция. В этом отличие от substring(start, end) в JavaScript и substring(begin, end) в Java, и отсюда много ошибок на несколько символов при переносе кода.

Вывод:

4271-BLK
SHOE
4271
BLK
0
SHOE-4271-BLK

Как и любой строковый метод, Substring не изменяет исходную строку; он возвращает новую, поэтому присваивайте результат.

ArgumentOutOfRangeException и как его избежать

Substring выбрасывает ArgumentOutOfRangeException, когда запрошенный диапазон не помещается в строку: отрицательный startIndex, startIndex больше Length, отрицательный length или startIndex + length больше Length. Никакого молчаливого усечения, как у срезов в Python, нет.

Вывод:

Substring(2, 4) -> "ffee"
Substring(2, 5) -> ArgumentOutOfRangeException
Substring(6) -> ""
Substring(7) -> ArgumentOutOfRangeException
Substring(-1) -> ArgumentOutOfRangeException
Substring(0, 10) -> ArgumentOutOfRangeException

Обычная защита ограничивает длину оставшейся частью строки через Math.Min:

Вывод:

ffee
ea
[]
[]

Вызов Substring у строки null выбрасывает NullReferenceException, а не ArgumentOutOfRangeException, поэтому вспомогательный метод сначала проверяет null.

Первые и последние n символов

Взять префикс или суффикс это самое частое применение Substring. Оба варианта выбрасывают исключение, если строка короче n, поэтому рабочий код проверяет длину:

Вывод:

**** 1111
An Unexpecte...
Hi
Hi

Когда нужно отбросить начало строки, достаточно одного Substring(n): "#FF8800".Substring(1) даёт "FF8800". Родственный метод Remove(start, count) делает обратное Substring: возвращает строку, из которой вырезан этот диапазон.

Substring вместе с IndexOf

Большинство подстрок в реальном коде находят, а не отсчитывают. IndexOf возвращает позицию символа или строки (или -1, если их нет), а LastIndexOf ищет с конца. Сочетайте их с Substring, чтобы вырезать текст вокруг разделителя:

Вывод:

maya.lopez
example.com
pdf
report.final.v2
WARN
Disk almost full

Арифметика подчиняется одному правилу: чтобы взять текст между позициями a и b (не включая их), начало равно a + 1, а длина равна b - a - 1.

Опасность в том, что IndexOf возвращает -1. email.Substring(0, -1) выбрасывает исключение, а хуже того, email.Substring(-1 + 1) молча возвращает всю строку. Всегда проверяйте:

Вывод:

shop.io
(none)

Split как альтернатива

Когда строка это список полей с разделителем, Split проще цепочки вызовов IndexOf и Substring, и в нём нет индексной арифметики, в которой можно ошибиться:

Вывод:

Ana Silva
Porto
maya.lopez
beach.jpg

Split выделяет массив плюс по одной строке на каждое поле, поэтому для одного разреза в горячем цикле IndexOf и Substring дешевле. Во всех остальных случаях важнее читаемость. Больше вариантов (несколько разделителей, удаление пустых элементов, максимальное количество) на странице методы строк. Для шаблонов вроде «цифры после ID:» обычно понятнее всего регулярное выражение.

Оператор диапазона (C# 8 и новее)

C# 8 добавил синтаксис индексов и диапазонов, который работает со строками и массивами. ^n означает «n-й с конца», а a..b это диапазон от a до b, не включая b, поэтому второе число это конечный индекс, а не длина:

// C# 8 and later
string sku = "SHOE-4271-BLK";

string first4 = sku[..4];      // "SHOE"        same as Substring(0, 4)
string middle = sku[5..9];     // "4271"        same as Substring(5, 4)
string last3  = sku[^3..];     // "BLK"         same as Substring(sku.Length - 3)
string noEnds = sku[1..^1];    // "HOE-4271-BL" drop first and last
char lastChar = sku[^1];       // 'K'

Для строки диапазон компилируется в вызов Substring, поэтому он выделяет новую строку и выбрасывает ArgumentOutOfRangeException в тех же случаях выхода за границы. Что использовать, дело стиля; диапазоны читаются лучше, когда концы отсчитываются с разных сторон ([1..^1]).

Подстроки без копирования: Span

Каждый вызов Substring выделяет новую строку. В коде разбора, где важна производительность (чтение больших файлов, обработка множества запросов), .NET Core 2.1 и новее предлагают ReadOnlySpan<char>, представление части исходной строки без копирования:

ReadOnlySpan<char> line = "2026-09-24,42.50,EUR".AsSpan();
ReadOnlySpan<char> amount = line.Slice(11, 5);          // "42.50", no allocation
decimal value = decimal.Parse(amount, provider: CultureInfo.InvariantCulture);

Span нельзя хранить в полях обычных классов или использовать через await, поэтому их место в компактном синхронном коде. Для типичного прикладного кода правильный инструмент Substring.

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

  • Передача конечного индекса вместо длины. s.Substring(2, 5) означает пять символов начиная с индекса 2, а не «с индекса 2 по 5». Чтобы взять текст от индекса a до индекса b, не включая его, длина равна b - a.
  • Не проверять IndexOf на -1. Результатом будет либо исключение, либо, с + 1, случайно вся строка.
  • Считать, что короткие строки достаточно длинные. Имя, код или строка из файла могут оказаться короче, чем вы ожидаете. Ограничивайте через Math.Min или проверяйте Length.
  • Отбрасывать результат. s.Substring(1); в отдельной строке ничего полезного не делает.

Часто задаваемые вопросы

Как работает Substring в C#?

s.Substring(start) возвращает символы от индекса start до конца; s.Substring(start, length) возвращает length символов, начиная с start. Индексы начинаются с 0, а второй аргумент это длина, а не конечный индекс: "Hello".Substring(1, 3) даёт "ell". Исходная строка не изменяется.

Почему Substring выбрасывает ArgumentOutOfRangeException?

Потому что start отрицательный или больше длины строки, либо start + length выходит за конец. Самая частая причина: длина, вычисленная для более длинной строки, или результат IndexOf, равный -1, использованный как начало. Ограничивайте длину через Math.Min(length, s.Length - start) и проверяйте результаты IndexOf перед использованием.

Как получить последние n символов строки в C#?

s.Substring(s.Length - n) возвращает последние n символов при условии n <= s.Length. Чтобы не падать на коротких строках, используйте s.Length <= n ? s : s.Substring(s.Length - n). В C# 8 и новее можно также написать s[^n..], который выбрасывает исключение так же, когда n слишком велико.

Как получить подстроку между двумя символами в C#?

Найдите обе позиции через IndexOf, затем возьмите часть между ними: int start = s.IndexOf('(') + 1; int end = s.IndexOf(')', start); string inside = s.Substring(start, end - start);. Перед вызовом Substring проверьте, что каждый IndexOf что-то нашёл (не -1). Для сложных шаблонов понятнее регулярное выражение.

Индексы в Substring начинаются с нуля?

Да. Первый символ находится по индексу 0, а последний по индексу Length - 1. Substring(0, 3) возвращает первые три символа. Передать в качестве начала сам Length разрешено: результатом будет пустая строка.

Coddy programming languages illustration

Учитесь программировать с Coddy

НАЧАТЬ