System.String一个有趣的方面是,一旦将初始值赋给字符串对象,字符数据就不能改变了。乍一看,这可能像是一个明显的谎言。因为我们总是给字符串赋新值,而且System.String类型也定义了许多用于以各种方式(大写、小写等)修改字符数据的方法。然而,如果细究背后发生的事情,就会注意到字符串类型的方法其实返回了一个按修改格式的新字符串对象。
static void StringAreImmutable() // 大写的s1? // 不!s1还是同样的格式! |
查看图3-12中相关的输出,就可以验证原来的string对象(s1)在调用ToUpper()之后没有变成大写,而是返回按修改后格式的字符串副本。
图3-12 字符串是不可变的 |
如果我们使用C#赋值运算符,同样的不变性命题还是成立的。例如,注释(或删除)StringAreImmutable()中既有的代码(减少生成的CIL代码的数量),然后增加如下的代码语句:
static void StringAreImmutable() |
现在,编译应用程序,然后把程序集加载到ildasm.exe中(同样,见第1章)。双击Main()方法就能发现如图3-13所示的CIL代码。
图3-13 为字符串对象赋值会产生一个新的字符串对象 |
尽管我们还没有研究CIL底层的一些细节,但是要注意到Main()方法多次调用了ldstr(加载字符串)操作码。简而言之,CIL的ldstr操作码在托管堆上加载了一个新的字符串对象。之前包含值"My other string."的字符串对象最终会被垃圾回收。
那么,我们能从中得出什么结论呢?简单来说,字符串类型如果被滥用,它就会变得低效,并导致代码膨胀,特别是进行字符串拼接的时候。如果我们需要表示在应用程序中用到的基本字符数据,如美国社会安全号码(SSN)、姓名或简单字符串文本等,字符串数据类型就是很好的选择。
然而,如果我们正在构建使用大量文本数据的应用程序(如字处理程序),那么使用字符串类型来表示字处理就是一个糟糕的主意,因为我们最后很可能(通常是间接地)产生字符串数据不必要的副本。