很久以前,成龙上演了一部《我是谁》,现在string也遭遇了相同的问题.无论在JAVA中还是.NET中它永远都是一种“明星”气质,考虑它总是要从特殊性的角度考虑,它属于基本数据类型,也是基本数据类型中唯一的引用类型。
现在我们知道,C#我们一直在使用字符串string关键字的映射实际上指向.NET 基类System.String。也就是说string 是 .NET Framework 中的 System.String 的别名,只是说string是C#里面的类而已。System.String是一个功能非常强大且用途非常广泛的基类,但它不是.NET中惟一与字符串相关的类,该类专门用于存储字符串,允许对字符串进行许多操作。
string 对象称为不可变的(只读),因为一旦创建了该对象,就不能修改该对象的值。看来似乎修改了 string 对象的方法实际上是返回一个包含修改内容的新 string 对象。
这句话初听起来似乎有些不可思议,大家也许马上会想到字符串的连接操作,我们不也可以改变字符串吗?看下面这段代码:
string name = “1234”; …….1
name = name + “5678”; …….2
Console.WriteLine(name); …….3
看起来我们似乎已经把name的值从“1234”改为了“12345678”。事实是这样的吗?实际上并没有改变。在第1行代码中创建了一个string对象它的值是“1234”,name指向了它在内存中的地址;第2行代码中创建了一个新的string对象它的值是“12345678”,name指向了新的内存地址。这时在堆中其实存在着两个字符串对象,尽管我们只引用了它们中的一个,但是字符串“1234”仍然在内存中驻留。
那么如果多次修改一个字符串,请使用System.Text.StringBuilder, 因为它是专门为这种情况设计的应用StringBuilder来创立字符串能显著的减少内存分配.
class Program
{
static void Main(string[] args)
{
string s1 = “”;
StringBuilder s2 = new StringBuilder();
DateTime dt1 = DateTime.Now;
for (int i = 0; i < 10000; i++)
{
s1 = s1 + i.ToString();
}
DateTime dt2 = DateTime.Now;
for (int i = 0; i < 10000; i++)
{
s2.Append(i.ToString());
}
DateTime dt3 = DateTime.Now;
Console.WriteLine(dt2-dt1);
Console.WriteLine(dt3-dt2);
}
}
从这里可以看出,当出现多字符串连接时,尽量使用StringBuilder。它简单而且直接,当然,一般情况下觉察不到这二者效率的差异,但如果你要对某个字符串进行大量的添加操作,那么StringBuilder类所耗费的时间和String类简直不是一个数量级的。
string 对象是引用类型,不是值类型。您也许马上会做以下实验:
class Program
{
static void Main(string[] args)
{
string myname = “abc”;
ChangeValue(myname);
Console.WriteLine(name);
}
static void ChangeValue(string name)
{
name = "efg";
}
}
结果:
abc
如果按照引用类型,传递的应该是地址,修改了name的值,那么myname的值应该也要做相应的修改,结果应该是”efg”,但事实与之相反。
现在string是引用类型是可以确定的:
- string继承自object,而不是System.ValueType(Int32这样的则是继承于System.ValueType) (大家可以做下实验,通过GetType()的BaseType属性)
- string本质上是个char[],而Array是引用类型,并且初始化时也是在托管堆分配内存的
那么上面的事实又怎么解释呢?
应该说name和myname是同一地址,你改了myname,本应该name的值也随着改变的,但这样就等于立刻创建了个新字符串,并把name指向新的。这时候myname还指向原来的。所以没变。string的所有所谓“改变”都创建了新的实例。