写在前面
在经过了一个昏天黑地的假日之后,终于决定要做点儿什么了。这次决定说说属于地球上的程序员的共同话题--字符串。
为什么这么说呢?其实很简单,字符串是人机交互的重要元素。用户通过键盘,向计算机传入的绝大部分内容都是字符串,而计算机的计算结果,也有相当一部分要转换为字符串向用户显示。字符串是一个程序员必须要处理,而且要处理好的问题。记得上学时,谭浩强的《C程序设计》中就用很大的篇幅介绍了字符串数组。
这第一篇,就以.NET Framework 中的字符串,String类入手。今后还要继续介绍我所接触过的String类,比如CString类、string(STL)类、BSTR 等等……
获得String 类代码
以前是用Reflector 现在可以通过Visual Studio 2008的源代码调试的方式获得String类的源代码。具体方法可以参考横刀天笑 的文章。要Load System.dll 的符号表文件,然后再将调试断点设定在一行字符串操作代码上,最后按F11键,就可以看到全部的String 类代码了。
“字符串是不可变的”以及字符串内存管理
这句话有点儿难理解。其实,这句话是指字符串占用的空间。字符串的内存空间一旦申请了,就没法随意的伸缩了。如果需要修改字符串内容的大小,比如添加几个字符,就需要重新向内存申请空间。在字符串使用者看来,这没什么。但是对于计算机和OS的内存管理来说,就是一个比较大的挑战。下面列举一种情况:假设你有一篇小诗要放入内存,大概占用了318个字节。你还有一篇文章要保存,占用了内存5K的空间。

此时,如果将318个字节释放掉,然后要放入一个800字节的字符串。那么对于内存管理来说可能就是一个噩梦。因为虽然6K的内存有1K的剩余空间,但是地址并不连续。其实,有足够的空间并不意味着就可以存放对象,还要看存放对象的空间内存地址是不是连续的。当然,对于上述情况,绝大多数情况下OS的内存管理是不会弹出一个“Out of memory”了事儿的。OS内存管理模块会依据算法将某些内存页调出,保存到磁盘,再修改内存映射表…… 总之,会让计算机忙上一阵子。
如果我们在堆内存中频繁的申请、释放一个或者几个字节,那么要不了多久,在可用内存总数不变的情况下,内存空间就会“千疮百孔”也就是我们常说的内存碎片。好在.NET CLR 的GC可以对内存碎片进行整理,但这也是一个非常耗时的工作。
那么对于这么头疼的问题有什么好办法么? 一种解决办法是,给内存设定阀值,比如,设定最小内存申请空间为:512个字节。当申请318个字符的存储空间时,OS实际上分配了512个字节。这样保证内存的申请和释放都是一些相对“完整”的内存“块”以减少内存碎片的产生。 还有一种办法就是尽量遵循“不可变”的原则,减少字符串在创建后的伸长和缩短的操作。
说了这么多,只是让看客们了解一些基础知识,以便更好的理解string类的代码。言归正传……
String 类概述
String 类源代码一共有3257行。作为容器(容纳字符的容器),String 类继承了IComparable、ICloneable、IConvertible、IEnumerable 接口,以便调用者枚举、拷贝、转换容器中的字符。在声明中还有下面的代码:
#if GENERICS_WORK
, IComparable<String>, IEnumerable<char>, IEquatable<String>
#endif
这表示源代码的编写者考虑到了对泛型的支持。如果使用C# 2.0 标准的编译器编译代码,那么String 类还需要继承这三个泛型接口。String 类的方法约有80个(不包括重载形式)。我大概给他们分了分类:
1) 内存申请函数,FastAllocateString。
2) 构造函数,提供了8种构造的重载形式用来构建字符串对象。
3) 字符串操作函数,例如Insert、Join、Substring等,可以对字符串的内容进行修改。
4) 字符串特征函数,例如IsNullOrEmpty、IsNormalize等,向调用者返回当前字符串内容的特征。
5) 格式化字符串函数,Format。
6) 接口实现函数,如String.Iconvertible.ToBoolean() 等。
7) 内部辅助函数。
内存申请函数
FastAllocateString(int iLength) 这个函数不在String.cs中实现,其声明为:
private extern static String FastAllocateString(int length); 具体实现不详(为此苦恼了很久……)。但从该函数的声明可以看出,该函数可以在托管堆上申请一片指定长度的内存,并返回一个String类型的引用地址。在String类中,凡是需要申请内存的地方,都是使用这个函数。
构造函数
在8种构造函数中,最基本的构造函数的声明:
[CLSCompliant(false), MethodImplAttribute(MethodImplOptions.InternalCall)]
unsafe public extern String(sbyte *value, int startIndex, int length, Encoding enc);
也就是说,要给定一个指向字符串首地址的指针(sbyte *value),要创建的字符串在给定的字符串中的起始索引位置(int startIndex),字符串长度(int length),以及编码对象(Encoding enc)。
其实,不论那种构造,最终都是调用CreateString静态函数创建的字符串对象。
1 unsafe static private String CreateString(sbyte *value, int startIndex, int length, Encoding enc) {
2
if (enc == null)
3
return new String(value, startIndex, length); // default to ANSI
4
if (length < 0)
5
throw new ArgumentOutOfRangeException("length",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
6
if (startIndex < 0) {
7
throw new ArgumentOutOfRangeException("startIndex",Environment.GetResourceString("ArgumentOutOfRange_StartIndex"));
8
}
9
if ((value + startIndex) < value)
{
10
// overflow check
11
throw new ArgumentOutOfRangeException("startIndex",Environment.GetResourceString("ArgumentOutOfRange_PartialWCHAR"));
12
}
13
byte [] b = new byte[length];
14
15
try {
16
Buffer.memcpy((byte*)value, startIndex, b, 0, length);
17
}
18
catch(NullReferenceException)
{
19
// If we got a NullReferencException. It means the pointer or
20
// the index is out of range
21
throw new ArgumentOutOfRangeException("value",
22
Environment.GetResourceString("ArgumentOutOfRange_PartialWCHAR"));
23
}
24
25
return enc.GetString(b);
26
}
该函数在进行过参数检查后,首先在堆内存中,按照指定的长度创建了缓冲区b,然后使用System.Buffer的内存拷贝函数memcpy将指定的字符串内容拷贝到缓冲区b,最后调用编码对象Encoding的GetString方法,得到指定编码方式下的,经过正确编码的字符串。
字符串操作函数
这部分包含的函数比较多,着重的说几个。Join函数,这个函数是用来进行字符串合并的函数。从这个函数里面,我们仍然可以看到字符串“不可变”的一面。代码如下:
1
public unsafe static String Join(String separator, String[] value, int startIndex, int count)
{
2
//Treat null as empty string.
3
if (separator == null) {
4
separator = String.Empty;
5
}
6
7
//Range check the array
8
if (value == null) {
9
throw new ArgumentNullException("value");
10
}
11
12
if (startIndex < 0) {
13
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_StartIndex"));
14
}
15
if (count < 0) {
16
throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NegativeCount"));
17
}
18
19
if (startIndex > value.Length - count)
{
20
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_IndexCountBuffer"));
21
}
22
23
//If count is 0, that skews a whole bunch of the calculations below, so just special case that.
24
if (count == 0) {
25
return String.Empty;
26
}
27
28
int jointLength = 0;
29
//Figure out the total length of the strings in value
30
int endIndex = startIndex + count - 1;
31
for (int stringToJoinIndex = startIndex; stringToJoinIndex <= endIndex; stringToJoinIndex++)
{
32
if (value[stringToJoinIndex] != null) {
33
jointLength += value[stringToJoinIndex].Length;
34
}
35
}
36
37
//Add enough room for the separator.
38
jointLength += (count - 1) * separator.Length;
39
40
// Note that we may not catch all overflows with this check (since we could have wrapped around the 4gb range any number of times
41
// and landed back in the positive range.) The input array might be modifed from other threads,
42
// so we have to do an overflow check before each append below anyway. Those overflows will get caught down there.
43
if ((jointLength < 0) || ((jointLength + 1) < 0) ) {
44
throw new OutOfMemoryException();
45
}
46
47
//If this is an empty string, just return.
48
if (jointLength == 0) {
49
return String.Empty;
50
}
51
52
string jointString = FastAllocateString( jointLength );
53
fixed (char * pointerToJointString = &jointString.m_firstChar) {
54
UnSafeCharBuffer charBuffer = new UnSafeCharBuffer( pointerToJointString, jointLength);
55
56
// Append the first string first and then append each following string prefixed by the separator.
57
charBuffer.AppendString( value[startIndex] );
58
for (int stringToJoinIndex = startIndex + 1; stringToJoinIndex <= endIndex; stringToJoinIndex++)
{
59
charBuffer.AppendString( separator );
60
charBuffer.AppendString( value[stringToJoinIndex] );
61
}
62
BCLDebug.Assert(*(pointerToJointString + charBuffer.Length) == \0', "String must be null-terminated!");
63
}
64
65
return jointString;
66
}
该函数,要求有4个参数。分隔符(String separator),要合并的小字符串组成的数组(String[] value),合并时数组的起始位置索引(int startIndex),要合并的小字符串数量(int count)。
和其他函数一样,首先要进行必要的参数检查。然后,根据参数,计算出未来合并出来的大字符串的总长度:
int jointLength = 0;
//Figure out the total length of the strings in value
int endIndex = startIndex + count - 1;
for (int stringToJoinIndex = startIndex; stringToJoinIndex <= endIndex; stringToJoinIndex++) {
if (value[stringToJoinIndex] != null) {
jointLength += value[stringToJoinIndex].Length;
}
}
//Add enough room for the separator.
jointLength += (count - 1) * separator.Length;
再然后,就是重新申请一个刚刚计算好长度的空间,用来存放新创建的字符串:
string jointString = FastAllocateString( jointLength );
看到了吧?新字符串不是以第一个小字符串,也不是以最后一个小字符串为基础,叠加出来的。而是在内存中重新申请了空间。换句话说,Join函数没有“改变”任何一个小字符串。证明了字符串声明了之后是“不可变的”。紧接着53-61行代码,就是通过指针把每个小字符串中的数据放到新创建的大缓冲区中。最后,返回这个新创建的String对象。
再说说Concat方法。这个方法我们经常使用的是它的另一种形式,那就是我们经常会写这样的代码 string s = s1 + s2 + s3;编译器在编译这行代码时,就会将多个字符串相加变为Concat函数的调用:Concat(s1,s2,s3);
1
2
public static String Concat(params Object[] args)
{
3
if (args==null) {
4
throw new ArgumentNullException("args");
5
}
6
7
String[] sArgs = new String[args.Length];
8
int totalLength=0;
9
10
for (int i=0; i<args.Length; i++)
{
11
object value = args;
12
sArgs= ((value==null)?(String.Empty):(value.ToString()));
13
totalLength += sArgs.Length;
14
// check for overflow
15
if (totalLength < 0) {
16
throw new OutOfMemoryException();
17
}
18
}
19
return ConcatArray(sArgs, totalLength);
20
}
这个Concat函数使用了一个可以支持可变参数的关键字params,代码的7-18行仍然是要计算新字符串的长度。第19行,调用了ConcatArray函数进行实质上的连接。
1
private static String ConcatArray(String[] values, int totalLength) {
2
String result = FastAllocateString(totalLength);
3
int currPos=0;
4
5
for (int i=0; i<values.Length; i++) {
6
BCLDebug.Assert((currPos <= totalLength - values.Length),
7
"[String.ConcatArray](currPos <= totalLength - values.Length)");
8
9
FillStringChecked(result, currPos, values);
10
currPos+=values.Length;
11
}
12
13
return result;
14
}
代码的第二行,就调用FastAllocateString方法申请了新字符串的空间,而后将字符串数组中的内容放入新的空间内。
字符串特征函数
IsNormalized 方法是用来表明当前字符串是否是被指定的规格规范化了,用以表明当前字符串是以何种编码格式存储的。至于IsAscii和IsFastSort都是外部实现的,无法看到代码。
格式化字符串函数
1
public static String Format( IFormatProvider provider, String format, params Object[] args)
{
2
if (format == null || args == null)
3
throw new ArgumentNullException((format==null)?"format":"args");
4
StringBuilder sb = new StringBuilder(format.Length + args.Length * 8);
5
sb.AppendFormat(provider,format,args);
6
return sb.ToString();
7
}
Format函数有多种重载,最基本的形式就是上面这样。参数有三:Format提供者(IFormatProvider provider), 格式字符串(String format)以及参数列表(params Object[] args)
代码中,在进行了参数检查后,创建了StringBuilder对象,然后调用了StringBuilder的AppendFormat方法完成了实际参数的填充动作。
1
public StringBuilder AppendFormat(IFormatProvider provider, String format, params Object[] args)
{
2
if (format == null || args == null) {
3
throw new ArgumentNullException((format==null)?"format":"args");
4
}
5
char[] chars = format.ToCharArray(0, format.Length);
6
int pos = 0;
7
int len = chars.Length;
8
char ch = '\x0';
9
10
ICustomFormatter cf = null;
11
if (provider!=null) {
12
cf=(ICustomFormatter)provider.GetFormat(typeof(ICustomFormatter));
13
}
14
15
while (true) {
16
int p = pos;
17
int i = pos;
18
while (pos < len) {
19
ch = chars[pos];
20
21
pos++;
22
if (ch == '}')
23
{
24
if(pos < len && chars[pos]=='}') // Treat as escape character for }}
25
pos++;
26
else
27
FormatError();
28
}
29
30
if (ch == '{')
31
{
32
if(pos < len && chars[pos]=='{') // Treat as escape character for {{
33
pos++;
34
else
35
{
36
pos--;
37
break;
38
}
39
}
40
41
chars[i++] = ch;
42
}
43
if (i > p) Append(chars, p, i - p);
44
if (pos == len) break;
45
pos++;
46
if (pos == len || (ch = chars[pos]) < '0' || ch > '9') FormatError();
47
int index =0;
48
do {
49
index = index * 10 + ch - '0';
50
pos++;
51
if (pos == len) FormatError();
52
ch = chars[pos];
53
} while (ch >= '0' && ch <= '9'
在经过了一个昏天黑地的假日之后,终于决定要做点儿什么了。这次决定说说属于地球上的程序员的共同话题--字符串。
为什么这么说呢?其实很简单,字符串是人机交互的重要元素。用户通过键盘,向计算机传入的绝大部分内容都是字符串,而计算机的计算结果,也有相当一部分要转换为字符串向用户显示。字符串是一个程序员必须要处理,而且要处理好的问题。记得上学时,谭浩强的《C程序设计》中就用很大的篇幅介绍了字符串数组。
这第一篇,就以.NET Framework 中的字符串,String类入手。今后还要继续介绍我所接触过的String类,比如CString类、string(STL)类、BSTR 等等……
获得String 类代码
以前是用Reflector 现在可以通过Visual Studio 2008的源代码调试的方式获得String类的源代码。具体方法可以参考横刀天笑 的文章。要Load System.dll 的符号表文件,然后再将调试断点设定在一行字符串操作代码上,最后按F11键,就可以看到全部的String 类代码了。
“字符串是不可变的”以及字符串内存管理
这句话有点儿难理解。其实,这句话是指字符串占用的空间。字符串的内存空间一旦申请了,就没法随意的伸缩了。如果需要修改字符串内容的大小,比如添加几个字符,就需要重新向内存申请空间。在字符串使用者看来,这没什么。但是对于计算机和OS的内存管理来说,就是一个比较大的挑战。下面列举一种情况:假设你有一篇小诗要放入内存,大概占用了318个字节。你还有一篇文章要保存,占用了内存5K的空间。

此时,如果将318个字节释放掉,然后要放入一个800字节的字符串。那么对于内存管理来说可能就是一个噩梦。因为虽然6K的内存有1K的剩余空间,但是地址并不连续。其实,有足够的空间并不意味着就可以存放对象,还要看存放对象的空间内存地址是不是连续的。当然,对于上述情况,绝大多数情况下OS的内存管理是不会弹出一个“Out of memory”了事儿的。OS内存管理模块会依据算法将某些内存页调出,保存到磁盘,再修改内存映射表…… 总之,会让计算机忙上一阵子。
如果我们在堆内存中频繁的申请、释放一个或者几个字节,那么要不了多久,在可用内存总数不变的情况下,内存空间就会“千疮百孔”也就是我们常说的内存碎片。好在.NET CLR 的GC可以对内存碎片进行整理,但这也是一个非常耗时的工作。
那么对于这么头疼的问题有什么好办法么? 一种解决办法是,给内存设定阀值,比如,设定最小内存申请空间为:512个字节。当申请318个字符的存储空间时,OS实际上分配了512个字节。这样保证内存的申请和释放都是一些相对“完整”的内存“块”以减少内存碎片的产生。 还有一种办法就是尽量遵循“不可变”的原则,减少字符串在创建后的伸长和缩短的操作。
说了这么多,只是让看客们了解一些基础知识,以便更好的理解string类的代码。言归正传……
String 类概述
String 类源代码一共有3257行。作为容器(容纳字符的容器),String 类继承了IComparable、ICloneable、IConvertible、IEnumerable 接口,以便调用者枚举、拷贝、转换容器中的字符。在声明中还有下面的代码:
#if GENERICS_WORK
, IComparable<String>, IEnumerable<char>, IEquatable<String>
#endif
这表示源代码的编写者考虑到了对泛型的支持。如果使用C# 2.0 标准的编译器编译代码,那么String 类还需要继承这三个泛型接口。String 类的方法约有80个(不包括重载形式)。我大概给他们分了分类:
1) 内存申请函数,FastAllocateString。
2) 构造函数,提供了8种构造的重载形式用来构建字符串对象。
3) 字符串操作函数,例如Insert、Join、Substring等,可以对字符串的内容进行修改。
4) 字符串特征函数,例如IsNullOrEmpty、IsNormalize等,向调用者返回当前字符串内容的特征。
5) 格式化字符串函数,Format。
6) 接口实现函数,如String.Iconvertible.ToBoolean() 等。
7) 内部辅助函数。
内存申请函数
FastAllocateString(int iLength) 这个函数不在String.cs中实现,其声明为:
private extern static String FastAllocateString(int length); 具体实现不详(为此苦恼了很久……)。但从该函数的声明可以看出,该函数可以在托管堆上申请一片指定长度的内存,并返回一个String类型的引用地址。在String类中,凡是需要申请内存的地方,都是使用这个函数。
构造函数
在8种构造函数中,最基本的构造函数的声明:
[CLSCompliant(false), MethodImplAttribute(MethodImplOptions.InternalCall)]
unsafe public extern String(sbyte *value, int startIndex, int length, Encoding enc);
也就是说,要给定一个指向字符串首地址的指针(sbyte *value),要创建的字符串在给定的字符串中的起始索引位置(int startIndex),字符串长度(int length),以及编码对象(Encoding enc)。
其实,不论那种构造,最终都是调用CreateString静态函数创建的字符串对象。
1 unsafe static private String CreateString(sbyte *value, int startIndex, int length, Encoding enc) {
2
if (enc == null) 3
return new String(value, startIndex, length); // default to ANSI 4
if (length < 0)5
throw new ArgumentOutOfRangeException("length",Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); 6
if (startIndex < 0) {7
throw new ArgumentOutOfRangeException("startIndex",Environment.GetResourceString("ArgumentOutOfRange_StartIndex"));8
}9
if ((value + startIndex) < value)
{ 10
// overflow check11
throw new ArgumentOutOfRangeException("startIndex",Environment.GetResourceString("ArgumentOutOfRange_PartialWCHAR")); 12
} 13
byte [] b = new byte[length];14
15
try {16
Buffer.memcpy((byte*)value, startIndex, b, 0, length);17
}18
catch(NullReferenceException)
{ 19
// If we got a NullReferencException. It means the pointer or20
// the index is out of range 21
throw new ArgumentOutOfRangeException("value", 22
Environment.GetResourceString("ArgumentOutOfRange_PartialWCHAR"));23
} 24

25
return enc.GetString(b);26
}该函数在进行过参数检查后,首先在堆内存中,按照指定的长度创建了缓冲区b,然后使用System.Buffer的内存拷贝函数memcpy将指定的字符串内容拷贝到缓冲区b,最后调用编码对象Encoding的GetString方法,得到指定编码方式下的,经过正确编码的字符串。
字符串操作函数
这部分包含的函数比较多,着重的说几个。Join函数,这个函数是用来进行字符串合并的函数。从这个函数里面,我们仍然可以看到字符串“不可变”的一面。代码如下:
1
public unsafe static String Join(String separator, String[] value, int startIndex, int count)
{2
//Treat null as empty string. 3
if (separator == null) { 4
separator = String.Empty;5
} 6

7
//Range check the array8
if (value == null) {9
throw new ArgumentNullException("value"); 10
}11
12
if (startIndex < 0) { 13
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_StartIndex"));14
} 15
if (count < 0) {16
throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NegativeCount"));17
}18
19
if (startIndex > value.Length - count)
{20
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_IndexCountBuffer")); 21
} 22

23
//If count is 0, that skews a whole bunch of the calculations below, so just special case that. 24
if (count == 0) {25
return String.Empty;26
}27
28
int jointLength = 0;29
//Figure out the total length of the strings in value 30
int endIndex = startIndex + count - 1; 31
for (int stringToJoinIndex = startIndex; stringToJoinIndex <= endIndex; stringToJoinIndex++)
{32
if (value[stringToJoinIndex] != null) { 33
jointLength += value[stringToJoinIndex].Length;34
}35
}36
37
//Add enough room for the separator.38
jointLength += (count - 1) * separator.Length; 39
40
// Note that we may not catch all overflows with this check (since we could have wrapped around the 4gb range any number of times41
// and landed back in the positive range.) The input array might be modifed from other threads, 42
// so we have to do an overflow check before each append below anyway. Those overflows will get caught down there.43
if ((jointLength < 0) || ((jointLength + 1) < 0) ) {44
throw new OutOfMemoryException();45
} 46

47
//If this is an empty string, just return. 48
if (jointLength == 0) { 49
return String.Empty;50
} 51

52
string jointString = FastAllocateString( jointLength );53
fixed (char * pointerToJointString = &jointString.m_firstChar) {54
UnSafeCharBuffer charBuffer = new UnSafeCharBuffer( pointerToJointString, jointLength); 55

56
// Append the first string first and then append each following string prefixed by the separator. 57
charBuffer.AppendString( value[startIndex] ); 58
for (int stringToJoinIndex = startIndex + 1; stringToJoinIndex <= endIndex; stringToJoinIndex++)
{59
charBuffer.AppendString( separator ); 60
charBuffer.AppendString( value[stringToJoinIndex] );61
}62
BCLDebug.Assert(*(pointerToJointString + charBuffer.Length) == \0', "String must be null-terminated!");63
} 64

65
return jointString; 66
}该函数,要求有4个参数。分隔符(String separator),要合并的小字符串组成的数组(String[] value),合并时数组的起始位置索引(int startIndex),要合并的小字符串数量(int count)。
和其他函数一样,首先要进行必要的参数检查。然后,根据参数,计算出未来合并出来的大字符串的总长度:
int jointLength = 0;
//Figure out the total length of the strings in value
int endIndex = startIndex + count - 1;
for (int stringToJoinIndex = startIndex; stringToJoinIndex <= endIndex; stringToJoinIndex++) {
if (value[stringToJoinIndex] != null) {
jointLength += value[stringToJoinIndex].Length;
}
}
//Add enough room for the separator.
jointLength += (count - 1) * separator.Length;
再然后,就是重新申请一个刚刚计算好长度的空间,用来存放新创建的字符串:
string jointString = FastAllocateString( jointLength );
看到了吧?新字符串不是以第一个小字符串,也不是以最后一个小字符串为基础,叠加出来的。而是在内存中重新申请了空间。换句话说,Join函数没有“改变”任何一个小字符串。证明了字符串声明了之后是“不可变的”。紧接着53-61行代码,就是通过指针把每个小字符串中的数据放到新创建的大缓冲区中。最后,返回这个新创建的String对象。
再说说Concat方法。这个方法我们经常使用的是它的另一种形式,那就是我们经常会写这样的代码 string s = s1 + s2 + s3;编译器在编译这行代码时,就会将多个字符串相加变为Concat函数的调用:Concat(s1,s2,s3);
1

2
public static String Concat(params Object[] args)
{ 3
if (args==null) {4
throw new ArgumentNullException("args");5
}6
7
String[] sArgs = new String[args.Length];8
int totalLength=0; 9
10
for (int i=0; i<args.Length; i++)
{11
object value = args; 12
sArgs= ((value==null)?(String.Empty):(value.ToString()));13
totalLength += sArgs.Length;14
// check for overflow15
if (totalLength < 0) { 16
throw new OutOfMemoryException();17
} 18
} 19
return ConcatArray(sArgs, totalLength);20
}这个Concat函数使用了一个可以支持可变参数的关键字params,代码的7-18行仍然是要计算新字符串的长度。第19行,调用了ConcatArray函数进行实质上的连接。
1
private static String ConcatArray(String[] values, int totalLength) {2
String result = FastAllocateString(totalLength); 3
int currPos=0;4
5
for (int i=0; i<values.Length; i++) { 6
BCLDebug.Assert((currPos <= totalLength - values.Length),7
"[String.ConcatArray](currPos <= totalLength - values.Length)"); 8

9
FillStringChecked(result, currPos, values);10
currPos+=values.Length;11
} 12

13
return result; 14
}代码的第二行,就调用FastAllocateString方法申请了新字符串的空间,而后将字符串数组中的内容放入新的空间内。
字符串特征函数
IsNormalized 方法是用来表明当前字符串是否是被指定的规格规范化了,用以表明当前字符串是以何种编码格式存储的。至于IsAscii和IsFastSort都是外部实现的,无法看到代码。
格式化字符串函数
1
public static String Format( IFormatProvider provider, String format, params Object[] args)
{ 2
if (format == null || args == null)3
throw new ArgumentNullException((format==null)?"format":"args");4
StringBuilder sb = new StringBuilder(format.Length + args.Length * 8);5
sb.AppendFormat(provider,format,args); 6
return sb.ToString();7
}Format函数有多种重载,最基本的形式就是上面这样。参数有三:Format提供者(IFormatProvider provider), 格式字符串(String format)以及参数列表(params Object[] args)
代码中,在进行了参数检查后,创建了StringBuilder对象,然后调用了StringBuilder的AppendFormat方法完成了实际参数的填充动作。
1
public StringBuilder AppendFormat(IFormatProvider provider, String format, params Object[] args)
{2
if (format == null || args == null) {3
throw new ArgumentNullException((format==null)?"format":"args");4
} 5
char[] chars = format.ToCharArray(0, format.Length);6
int pos = 0; 7
int len = chars.Length; 8
char ch = '\x0';9
10
ICustomFormatter cf = null;11
if (provider!=null) {12
cf=(ICustomFormatter)provider.GetFormat(typeof(ICustomFormatter));13
} 14

15
while (true) { 16
int p = pos; 17
int i = pos;18
while (pos < len) { 19
ch = chars[pos];20

21
pos++;22
if (ch == '}') 23
{24
if(pos < len && chars[pos]=='}') // Treat as escape character for }} 25
pos++; 26
else27
FormatError(); 28
}29

30
if (ch == '{')31
{ 32
if(pos < len && chars[pos]=='{') // Treat as escape character for {{33
pos++; 34
else 35
{36
pos--; 37
break;38
}39
}40
41
chars[i++] = ch;42
} 43
if (i > p) Append(chars, p, i - p); 44
if (pos == len) break;45
pos++; 46
if (pos == len || (ch = chars[pos]) < '0' || ch > '9') FormatError();47
int index =0;48
do {49
index = index * 10 + ch - '0'; 50
pos++;51
if (pos == len) FormatError(); 52
ch = chars[pos]; 53
} while (ch >= '0' && ch <= '9'