string 实例是不可变的。创建后无法更改。任何看似更改字符串的操作都会返回一个新实例:
string foo = "Foo";
// returns a new string instance instead of changing the old one
string bar = foo.Replace('o', 'a');
string baz = foo + "bar"; // ditto here
不可变对象有一些不错的属性,例如它们可以跨线程使用而不必担心同步问题,或者您可以直接分发您的私有支持字段而不必担心有人更改了他们不应该更改的对象(请参阅数组或可变列表,如果不需要,通常需要在返回它们之前复制它们)。但是如果使用不小心,它们可能会产生严重的性能问题(几乎任何事情 - 如果您需要一个以执行速度为荣的语言的示例,那么请查看 C 的字符串操作函数)。
当您需要一个 可变 字符串时,例如您正在分段构建或更改很多内容的字符串,那么您将需要一个 StringBuilder,它是一个字符缓冲区可以改变。这在很大程度上会影响性能。如果您想要一个可变字符串,而是使用普通的 string 实例来执行它,那么您最终会不必要地创建和销毁大量对象,而 StringBuilder 实例本身会发生变化,从而不需要许多新对象.
简单的例子:下面的例子会让很多程序员痛不欲生:
string s = string.Empty;
for (i = 0; i < 1000; i++) {
s += i.ToString() + " ";
}
您最终将在此处创建 2001 个字符串,其中 2000 个被丢弃。使用 StringBuilder 的相同示例:
StringBuilder sb = new StringBuilder();
for (i = 0; i < 1000; i++) {
sb.Append(i);
sb.Append(' ');
}
这应该减少内存分配器的压力:-)
但是应该注意,C# 编译器在处理字符串时相当聪明。比如下面这行
string foo = "abc" + "def" + "efg" + "hij";
将被编译器加入,在运行时只留下一个字符串。类似地,诸如
之类的行
string foo = a + b + c + d + e + f;
将被改写为
string foo = string.Concat(a, b, c, d, e, f);
因此您不必为五个无意义的串联付费,这将是一种天真的处理方式。这不会像上面那样将您保存在循环中(除非编译器展开循环,但我认为只有 JIT 可能实际上这样做,最好不要打赌)。