老早之前,有一个很有意思的帖子(string 引用类型,还是值类型),引来了热烈的讨论。当时把它给收藏了。这几天,闲来无事,翻看收藏夹的时候,又看到了它,觉得很有意思,研究了研究。有了一点点拙见。
首先,String是引用类型,这肯定是勿庸置疑的。String是继承自System.Object类型的,而不是象Int32一样,继承自System.ValueType。但是String也是一个比较奇特的类型,一般而言,一个引用类型,构造的时候,是调用的IL指令:newobj。但是String有它自己的特殊的IL指令:ldstr。这说明,虽然String也是引用类型,但是,它和其他的引用类型还是有一些区别的。具体而言,CLR使用了字符串驻留技术,来提升String的性能。
简单的说,字符串驻留技术保证:内容相同的字符串,其在托管堆栈上的地址也是相同的,这样,就避免了大量的相同的String实例的存在 ,从而提高程序的性能,下面的代码可以验证这一点。
string s = "1112";
MessageBox.Show(object.ReferenceEquals(s,"1112").ToString());
上面的结果,是True。
此外,字符串,还有一个特性,是恒定性。qqchen对这个也做了讨论。
我们可以看看下面的代码。
unsafe{
string s = "123";
string b = "123";
fixed(char* p = s)
{
System.IntPtr ip = (System.IntPtr)p;
Console.WriteLine("这是s的初始地址,{0}",ip.ToString());
}
fixed(char* p = b)
{
System.IntPtr ip = (System.IntPtr)p;
Console.WriteLine("这是b的地址,{0}",ip.ToString());
}
s = "234";
fixed(char* p = s)
{
System.IntPtr ip = (System.IntPtr)p;
Console.WriteLine("这是s的修改后地址,{0}",ip.ToString());
}
fixed(char* p = b)
{
System.IntPtr ip = (System.IntPtr)p;
Console.WriteLine("这是b在s修改后的地址,{0}",ip.ToString());
}
}
从上面的代码中,我们不难看出,s和b,一开始的时候,其地址是一样的,修改了s之后,s的地址就改变了,而b还是不变的。这就是string的恒定性和字符串驻留的具体体现。
我主要想说的,是String作为参数传递的问题。同所有的引用类型一致,String作为参数传递的时候,传递了地址的拷贝,就象值类型传递的是值拷贝一样。
有时候,可能会有这样的迷惑,既然String是按照地址来传递的,那么我在方法内部修改了String,为什么外部的String没有受到影响?比如下面的代码:
string s = “初始值”;
public void ChangeStr(string varStr)
{
varStr = “修改了!“;
}
在调用ChangeStr(s)之后,s的内容仍然是“初始值”,这是为什么呢?
其原因就是:虽然varStr和s的地址,开始的时候是一致的,但是在修改了varStr的内容之后,他们的地址就不一致了!这个在上面的代码中其实可以体现出来。
我们还是用代码进行验证:
unsafe
{
string s = "123";
fixed(char* p = s)
{
System.IntPtr ip = (System.IntPtr)p;
Console.WriteLine("这是s的初始地址,{0}",ip.ToString());
}
ChangedStr(s);
}
public void ChangedStr(string s)
{
unsafe
{
fixed(char* p = s)
{
System.IntPtr ip = (System.IntPtr)p;
Console.WriteLine("这是参数s的初始地址,{0}",ip.ToString());
}
s = "134";
fixed(char* p = s)
{
System.IntPtr ip = (System.IntPtr)p;
Console.WriteLine("这是参数s修改后的地址,{0}",ip.ToString());
}
}
上面代码的运行结果,是,参数s修改后的地址,已经和传递进来的地址不一样了。
这样,我们就明白了,【在调用ChangeStr(s)之后,s的内容仍然是“初始值”,这是为什么呢?】这个问题。
如果我们要得到这个修改后的结果,那么,只能用ref来修改传递的规则。public void ChangedStr(ref string s),这样,才能得到修改后的值。