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

本文关键字:意外 字符串 | 更新日期: 2023-09-27 18:15:45

.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

为什么不对case[2]抛出异常??

字符串有3个字符,所以索引是[0, 1, 2],甚至ToArray(), ToCharArray()方法返回3个字符!

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

如果我尝试以3开始索引Substring(),它不应该抛出异常吗?

文档非常明确地说明了这是正确的行为:

返回值:与本实例中以startIndex开头的子字符串相等的字符串,或者如果startIndex等于本实例的长度则为空

如果startIndex小于0或*大于此实例的长度,

抛出ArgumentOutOfRangeException。*

换句话说,从最后一个字符后面开始的子字符串将得到一个空字符串。

您希望它给您字符串的部分的注释与此不兼容。"字符串的一部分"也包括所有长度为零的子字符串的集合,s.substring(n, 0)给出一个空字符串。

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

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。注意,字符串的最后一个字符后面有一个栅栏柱。用这个栅栏柱调用函数将返回一个值,表示在此点之后没有栅栏板(即。它返回空的string)。

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

有时查看代码会很方便:

首先是

public string Substring(int startIndex)
{
    return this.Substring(startIndex, this.Length - startIndex);
}

由于value的减法,长度为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等于本实例的长度则为空。

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

*

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

与长度为length的子字符串长度相等的字符串在此实例中,从startIndex开始,如果startIndex相等,则为空到此实例的长度,且length为0。

Substring的作用是检查startIndex是否大于字符串的长度,然后才抛出异常。在你的例子中,它是相等的(字符串长度为3)。之后,它检查子字符串的长度是否为零,如果是则返回string . empty。在您的例子中,子字符串的长度是字符串(3)减去startIndex(3)的长度。这就是子字符串的长度为0并返回空字符串的原因。

c#中所有的字符串最终都有String.Empty .

这个问题有一个很好的答案。

From MSDN - String Class (System):

在。net框架中,String对象可以包含嵌入的null字符,作为字符串长度的一部分。然而,在在某些语言(如C和c++)中,null字符表示结束弦的;它不被认为是字符串的一部分,也不是作为字符串长度的一部分。

为了补充其他答案,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。如果长度等于0,则为空。