C#中string的一些特性
C#中string类型是一个比较特别的类型,它是一种引用类型,但在使用中,它表现的像一个值类型一样。这是因为string是不可变的(immutable)。
string具有以下的一些特性:
1. string是一个字符序列,是String类的一个别名,别且它是一个关键字。
2. string是引用类型,每个string实例是一个常量,是不可变的,因此对一个string进行修改时,实际上都是创建了一个新实例。
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)中搜索是否存在需要的字符串,若有则直接返回,否则创建一个新的副本到拘留池中。
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 }