C#中子字符串的意外行为

.net System.String类中的Substring()方法的定义是这样的

 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]抛出exception?

该字符串有3个字符,所以索引是[0, 1, 2] ,甚至ToArray()ToCharArray()方法返回3个字符! 如果我尝试使用起始索引3 Substring() ,它不应该抛出exception吗?

文档非常清楚这是正确的行为:

返回值:一个字符串,相当于在此实例中以startIndex开头的子字符串, 如果startIndex等于此实例的长度,则为Empty。

如果startIndex小于零或*大于此实例的长度,则抛出ArgumentOutOfRangeException *

换句话说,从最终字符之外开始的子字符串将为您提供一个空字符串。

您希望它给您一部分字符串的评论与此不相容。 “字符串的一部分”也包括零长度的所有子串的集合,如s.substring(n, 0) 将给出空字符串这一事实所certificate。

这里有很多技术答案说这个框架如何处理方法调用,但是我想通过类比给出一个推理, 为什么它就像它一样。

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)返回一个以fencepost n开头的面板string 。 请注意,该字符串的最后一个字符后面有一个fence栏。 使用此fence post调用该函数会返回一个值,表明此点之后没有fence面板(即,它返回空string )。

类似地, string.Substring(n, l)返回以fencepost n开头的l面板的string 。 这就是为什么像"ABC".Substring(2, 0)这样的东西"ABC".Substring(2, 0)返回""

有时查看代码可能很方便 :

首先这叫做:

 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); } 

根据MSDN上写的内容:

*

返回值 – 一个字符串,它等于在此实例中以startIndex开头的子字符串,如果startIndex等于此实例的长度,则为Empty。

exception ArgumentOutOfRangeException – startIndex小于零或大于此实例的长度

*

查看String.Substring方法文档,如果起始索引等于长度,将返回空字符串。

一个字符串,它等于在此实例中以startIndex开头的长度长度的子字符串,如果startIndex等于此实例的长度且长度为零,则为Empty。

Substring的作用是检查startIndex是否大于字符串的长度,然后它才会抛出exception。 在你的情况下它是相等的(字符串长度是3)。 之后,它检查子串的长度是否为零,如果返回String.Empty。 在您的情况下,子字符串的长度是字符串(3)的长度减去startIndex(3)。 这就是子串的长度为0并返回空字符串的原因。

C#中的所有字符串最后都有String.Empty

这个问题的答案很好 。

从MSDN – String类(系统):

在.NET Framework中,String对象可以包含嵌入的空字符,这些字符计为字符串长度的一部分。 但是,在某些语言(如C和C ++)中,空字符表示字符串的结尾; 它不被视为字符串的一部分,不计入字符串长度的一部分。

为了补充其他答案,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。