Неожиданное поведение подстроки в С#
Определение метода Substring()
в классе .net System.String
похоже на это
public string Substring(int startIndex)
Где startIndex
- "Начальная позиция символа нулевой основы подстроки в этом экземпляре" в соответствии с определением метода. Если я правильно понимаю, это означает, что он даст мне часть строки, начиная с нулевого индекса.
Теперь, если у меня есть строка "ABC"
и взять подстроку с разными индексами, я получаю следующие результаты.
var str = "ABC";
var chars = str.ToArray(); //returns 3 char 'A', 'B', 'C' as expected
var sub2 = str.Substring(2); //[1] returns "C" as expected
var sub3 = str.Substring(3); //[2] returns "" ...!!! Why no exception??
var sub4 = str.Substring(4); //[3] throws ArgumentOutOfRangeException as expected
Почему это не исключает исключение для случая [2]??
Строка имеет 3 символа, поэтому индексы [0, 1, 2]
, и даже ToArray()
, ToCharArray()
метод возвращает 3 символа, как ожидалось! Не следует ли это исключать, если я пытаюсь Substring()
с начальным индексом 3
?
Ответы
Ответ 1
Документация довольно четко говорит о правильном поведении:
Возвращаемое значение: строка, эквивалентная подстроке, начинающейся с startIndex в этом экземпляре, или Пустой, если startIndex равен длине этого экземпляра.
Выбрасывает ArgumentOutOfRangeException
, если startIndex
меньше нуля или * больше длины этого экземпляра. *
Другими словами, взятие подстроки, начинающейся чуть выше финального символа, даст вам пустую строку.
Ваш комментарий, что вы ожидали, что он даст вам часть строки, несовместим с этим. "Часть строки" также включает в себя множество всех подстрок нулевой длины, о чем свидетельствует тот факт, что s.substring(n, 0)
также даст пустую строку.
Ответ 2
Здесь есть много технических ответов, в которых говорится о том, как структура обрабатывает вызов метода, но я хотел бы дать рассуждения по аналогии, почему это так.
Рассмотрим string
как забор, где сами панели ограждений являются символами, зажатыми с заборами, пронумерованными, как показано ниже:
0 1 2 3
| A | B | C | "ABC"
0 1 2 3 4 5 6 7 8 9
| M | y | | S | t | r | i | n | g | "My String"
В этой аналогии string.Substring(n)
возвращает a string
панелей, начинающихся с fencepost n
. Обратите внимание, что последний символ строки имеет после него забор. Вызов функции с этим столбом забора возвращает значение, указывающее, что после этой точки нет панелей ограждений (т.е. Он возвращает пустой string
).
Аналогично, string.Substring(n, l)
возвращает string
панелей l
, начиная с fencepost n
. Вот почему что-то вроде "ABC".Substring(2, 0)
также возвращает ""
.
Ответ 3
Иногда просмотр кода может быть удобным:
Сначала это называется:
public string Substring(int startIndex)
{
return this.Substring(startIndex, this.Length - startIndex);
}
Длина равна 0 из-за вычитания значения:
public string Substring(int startIndex, int length)
{
if (startIndex < 0)
{
throw new ...
}
if (startIndex > this.Length)
{
throw new ...
}
if (length < 0)
{
throw new ...
}
if (startIndex > (this.Length - length))
{
throw new ...
}
if (length == 0) // <-- NOTICE HERE
{
return Empty;
}
if ((startIndex == 0) && (length == this.Length))
{
return this;
}
return this.InternalSubString(startIndex, length);
}
Ответ 4
Основываясь на том, что написано на MSDN:
*
Возвращаемое значение. Строка, эквивалентная подстроке, начинающейся с startIndex в этом экземпляре, или Пустое, если startIndex равно длине этого экземпляра.
Исключения
ArgumentOutOfRangeException - startIndex меньше нуля или больше длины этого экземпляра
*
Ответ 5
Если посмотреть на String.Substring Method, пустая строка будет возвращена, если начальный индекс равен длине.
Строка, эквивалентная подстроке длины длины, которая начинается с startIndex в этом экземпляре или Empty, если startIndex равен к длине этого экземпляра, а длина равна нулю.
Ответ 6
Что такое Substring, так это то, что он проверяет, является ли startIndex больше длины строки, и только тогда она выдает исключение. В вашем случае он равен (длина строки равна 3). После этого он проверяет, равна ли длина подстроки нулевой, а если она возвращает String.Empty. В вашем случае длина подстроки равна длине строки (3) минус startIndex (3). Вот почему длина подстроки равна 0 и возвращается пустая строка.
Ответ 7
Все строки в С# в конце имеют String.Empty
.
Вот хороший ответ по этому вопросу.
От MSDN - String
Класс (система):
В .NET Framework объект String может включать внедренный null символов, которые считаются частью длины строки. Однако в некоторые языки, такие как C и С++, нулевой символ указывает конец строки; он не считается частью строки и не является считается частью длины строки.
Ответ 8
Чтобы дополнить другие ответы, Mono также правильно реализует это поведение.
public String Substring (int startIndex)
{
if (startIndex == 0)
return this;
if (startIndex < 0 || startIndex > this.length)
throw new ArgumentOutOfRangeException ("startIndex");
return SubstringUnchecked (startIndex, this.length - startIndex);
}
// This method is used by StringBuilder.ToString() and is expected to
// always create a new string object (or return String.Empty).
internal unsafe String SubstringUnchecked (int startIndex, int length)
{
if (length == 0)
return String.Empty;
string tmp = InternalAllocateStr (length);
fixed (char* dest = tmp, src = this) {
CharCopy (dest, src + startIndex, length);
}
return tmp;
}
Как вы можете видеть, он возвращает String.Empty, если длина равна нулю.