C#中string类型是一个比较特别的类型,它是一种引用类型,但在使用中,它表现的像一个值类型一样。这是因为string是不可变的(immutable)。
string具有以下的一些特性:
1. string是一个字符序列,是String类的一个别名,别且它是一个关键字。
2. string是引用类型,每个string实例是一个常量,是不可变的,因此对一个string进行修改时,实际上都是创建了一个新实例。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
2 {
3
4 string str = " First " ;
5 string str_1 = str; // str_1与str指向同一个实例
6 Console.WriteLine( string .ReferenceEquals(str, str_1)); // 结果是True
7 str += " New " ; // 修改str的值
8 Console.WriteLine( string .ReferenceEquals(str, str_1)); // 结果是False
9 }
3. 创建一个string实例时, 首先会从拘留池(Intern pool)中搜索是否存在需要的字符串,若有则直接返回,否则创建一个新的副本到拘留池中。
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![ExpandedBlockStart.gif](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
2 {
3 string str = " First " ;
4 Console.WriteLine( string .IsInterned(str) != null ); // 结果是True
5
6 string str_1 = " First " ; // str_1的值与str相同
7 Console.WriteLine( string .IsInterned(str_1) != null ); // 结果是True
8
9 Console.WriteLine( string .ReferenceEquals(str, str_1)); // 结果是True,str和str_1指向同一个实例
10 }
4. 编译器会对字符串的操作进行优化。
2 {
3 string str = " First " + " Second " ; // 看上去是创建了两个字符串实例
4 Console.WriteLine(str);
5 }
看上去,好像是先创建了两个字符串实例,实际上编译器已经对这种能明确结果的运算进行了优化,在MSIL中:
2 {
3 .entrypoint
4 // 代码大小 15 (0xf)
5 .maxstack 1
6 .locals init ([ 0 ] string str)
7 IL_0000: nop
8 IL_0001: ldstr " FirstSecond "
9 IL_0006: stloc.0
10 IL_0007: ldloc.0
11 IL_0008: call void [mscorlib]System.Console::WriteLine( string )
12 IL_000d: nop
13 IL_000e: ret
14 } // end of method Program::Main
在文章中还有对其他情况进行更详细的实验。
另外,我们会经常判断一个字符串是否为空,使用str.Length == 0速度最快,这点网上有很多比较了,自己证明一下也很容易。使用Reflector查看了string.IsNullOrEmpty()的具体实现后,也的确是用这样的判断方法。
2 {
3 if (value != null )
4 {
5 return (value.Length == 0 );
6 }
7 return true ;
8 }
还有,是关于string.Empty与“”的区别,通过Reflector,能够看到string.Empty是这样定义和初始化的:
2
3 static String()
4 {
5 Empty = "" ;
6 WhitespaceChars = new char [] {
7 ' \t ' , ' \n ' , ' \v ' , ' \f ' , ' \r ' , ' ' , ' \x0085 ' , ' \x00a0 ' , ' ' , ' ' , ' ' , ' ' , ' ' , ' ' , ' ' , ' ' ,
8 ' ' , ' ' , ' ' , ' ' , ' ' , ' \u2028 ' , ' \u2029 ' , ' ' , ' '
9 };
10 }
那么我们这样使用
2 {
3 string str = string .Empty;
4 string str_1 = "" ;
5 Console.WriteLine( string .IsInterned(str) != null ); // 结果是True
6 Console.WriteLine( string .IsInterned(str_1) != null ); // 结果是True
7 Console.WriteLine( string .ReferenceEquals(str,str_1)); // 结果是True
8 }
str和str_1的指向是同一个实例。这说明了string.Empty与""其实是一样的,有点小区别的,就是使用string.Empty的性能要比""稍微好点,因为使用string.Empty则会直接指向这个静态变量值,而直接使用""会有一次搜索拘留池的操作。
最后,来个比较全的例子对string的拘留池以及在内存中创建的string实例进行总结下:
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace ConsoleApplication1
7 {
8 class Program
9 {
10 static void Main( string [] args)
11 {
12 string a = new StringBuilder().Append( ' a ' ).ToString();
13 string b = new StringBuilder().Append( ' b ' ).ToString();
14 string c = new StringBuilder().Append( ' c ' ).ToString();
15 string d = new StringBuilder().Append( ' d ' ).ToString();
16 string e = new StringBuilder().Append( ' a ' ).Append( ' b ' ).ToString();
17 string f = new StringBuilder().Append( ' e ' ).Append( ' f ' ).ToString();
18 string g = new StringBuilder().Append( ' m ' ).Append( ' n ' ).ToString();
19 string h = new StringBuilder().Append( ' e ' ).ToString();
20 string m = new StringBuilder().Append( ' m ' ).ToString();
21 string n = new StringBuilder().Append( ' n ' ).ToString();
22
23 Console.WriteLine( string .IsInterned(a) != null ); // 结果为False,拘留池中没有储存"a"
24 Console.WriteLine( string .IsInterned(b) != null ); // 结果为True,因为本函数中(下面b1变量)已经有明文字符串"b",则会被添加到拘留池
25 Console.WriteLine( string .IsInterned(c) != null ); // 结果为False,拘留池中还没有储存"c",注意调用Test()后的结果
26 Console.WriteLine( string .IsInterned(d) != null ); // 结果为False,拘留池中还没有储存"d",注意实例化TestClass后的结果
27 Console.WriteLine( string .IsInterned(e) != null ); // 结果为False,拘留池中没有储存"ab"
28 Console.WriteLine( string .IsInterned(f) != null ); // 结果为True,下面f1变量中的"e"+"f"会被优化成"ef",将"ef"添加到拘留池
29 Console.WriteLine( string .IsInterned(g) != null ); // 结果为False,拘留池中没有储存"mn"
30 Console.WriteLine( string .IsInterned(h) != null ); // 结果为False,下面f1变量中的"e"+"f"会被优化成"ef",不会有"e"被添加到拘留池
31 Console.WriteLine( string .IsInterned(m) != null ); // 结果为True,给g1变量初始化时,将"m"添加到拘留池
32 Console.WriteLine( string .IsInterned(n) != null ); // 结果为True,给g1变量初始化时,将"n"添加到拘留池
33
34 Test();
35 Console.WriteLine( string .IsInterned(c) != null ); // 结果为True,调用的Test()方法中,已经将"c"添加拘留池中
36 TestClass testClass = new TestClass();
37 Console.WriteLine( string .IsInterned(d) != null ); // 结果为True,实例化TestClass时,已经将"d"添加拘留池中
38
39 string b1 = " b " ; // CLR会将当前的方法中所有出现的的明文字符串(优化后的如"x"+"y"则被优化为"xy")添加到拘留池,然后才开始运行,故变量b1虽然定义在后面,但"b"已经被添加到拘留池了
40 string f1 = " e " + " f " + a; // 编译器会优化为"ef"+a,并将"ef"添加到拘留池,不会将"e"和"f"添加到拘留池
41 string g1 = " m " + a + " n " ; // 将"m"和"n"添加到拘留池
42 string f2 = " efa " ; // 将"efa"添加到拘留池
43 string f3 = " e " + " f " + " a " ; // 编译器会优化为"efa",f3将指向拘留池的内存
44
45 // 上述string.IsInterned()方法只是查看变量的值是否在添加到拘留池中,并不表示变量指向拘留池的内存
46 // 事实上非明文字符串的变量都将指向开辟新内存
47
48 Console.WriteLine( string .ReferenceEquals(b1, b)); // 结果为False,变量b的值"b"在拘留池中已经存在,但b指向的是新开辟内存,b1指向的是拘留池的内存
49 Console.WriteLine( string .ReferenceEquals(b1, " b " )); // 结果为True
50
51 Console.WriteLine( string .ReferenceEquals(f1, f2)); // 结果为False,f1指向的是"ef"+a运算时新开辟的内存,f2指向的是拘留池的内存
52 Console.WriteLine( string .ReferenceEquals(f2, f3)); // 结果为True,f3被编译器优化为"efa",因此也指向拘留池的内存
53 Console.WriteLine( string .ReferenceEquals(f3, " efa " )); // 结果为True
54 }
55
56 static void Test()
57 {
58 string c1 = " c " ;
59 }
60 }
61 public class TestClass
62 {
63 string d1 = " d " ;
64 }
65 }