大家都知道,C#中的string是一个引用类型,String对象是存放在堆上,而不是堆栈上的,因此,当把一个字符串变量赋给另一个字符串时,会得到对内存中同一个字符串的两个引用。但是大家有没有想过,为什么修改其中一个字符串,另外一个不受影响呢?
原来,当我们把一个字符串变量赋给另一个字符串时,就会创建一个全新的String对象,就是说这个时候就会有两个对象,比如:
输出结果为:
也就是说,改变s1的值并没有对s2造成任何影响,这与我们平时所说的引用类型的行为正好相反。当用值"original string"初始化s1时,就在堆上分配了一个String对象。在初始化s2时,引用也指向这个对象,所以s2的值也是"original string"。但是现在要改变s1的值,而不是替换原来的值时,堆上就会为新值分配一个新对象。s2变量仍然指向原来的对象,所以它的值没有改变。
另外,如果我们像下面这样:
当我们用System.Object.Equals(str1,str2)比较时,返回值是true;按理说str1和str2应该指向不同的空间,应该返回false才对啊。原来Equals有三个版本:
前两个实例方法内部会调用CompareOrdinal静态方法,它会字符串中的各个字符,如果相等就返回true。第三个首先会检查两个引用指向的是否是同一个对象,如果是,就返回true,不再去比较各个字符了。
其实CLR使用了一种叫字符串驻留的技术,对于
当CLR初始化时,会创建一个内部的散列表,其中的键为字符串,值为指向托管堆中字符串的引用。刚开始,散列表为空,JIT编译器编译方法时,会在散列表中查找每一个文本常量字符串,首先会查找"abc"字符串,并且因为没有找到,编译器会在托管堆中构造一个新的指向"abc"的String对象引用,然后将"abc"字符串和指向该对象的引用添加到散列表中。
接着,在散列表中查找第二个"abc",这一次由于找到了该字符串,所以编译器不会执行任何操作,代码中再没有其它的文本常量字符串,编译器的任务完成,代码开始执行。执行时,CLR发现第一个语句需要一个"abc"字符串引用,于是,CLR会在内部的散列表中查找"abc",并且会找到,这样指向先前创建的String对象的引用就被保存在变量s1中,执行第二条语句时,CLR会再一次在散列表中查找"abc",并且仍然会找到,指向同一个String 对象的引用会被保存在变量s2中,到此s1和s2指向了同一个引用,所以System.Object.Equals(s1,s2)就会返回true了。
另外,C#中是不允许用new操作符创建String对象的,编译器会报错。
原来,当我们把一个字符串变量赋给另一个字符串时,就会创建一个全新的String对象,就是说这个时候就会有两个对象,比如:
class
StringExc
{
public static void Main()
{
string s1 = " original string " ;
string s2 = s1; // 注意此时会创建一个新对象
Console.WriteLine( " s1 is " + s1 );
Console.WriteLine( " s2 is " + s2 );
s1 = " changed string " ;
Console.WriteLine( " s1 is now " + s1 );
Console.WriteLine( " s2 is now " + s2 );
}
}
{
public static void Main()
{
string s1 = " original string " ;
string s2 = s1; // 注意此时会创建一个新对象
Console.WriteLine( " s1 is " + s1 );
Console.WriteLine( " s2 is " + s2 );
s1 = " changed string " ;
Console.WriteLine( " s1 is now " + s1 );
Console.WriteLine( " s2 is now " + s2 );
}
}
输出结果为:
s1
is
original
string
s2 is original string
s1 is now changed string
s2 is now original string
s2 is original string
s1 is now changed string
s2 is now original string
也就是说,改变s1的值并没有对s2造成任何影响,这与我们平时所说的引用类型的行为正好相反。当用值"original string"初始化s1时,就在堆上分配了一个String对象。在初始化s2时,引用也指向这个对象,所以s2的值也是"original string"。但是现在要改变s1的值,而不是替换原来的值时,堆上就会为新值分配一个新对象。s2变量仍然指向原来的对象,所以它的值没有改变。
另外,如果我们像下面这样:
string
str1
=
"
abc
"
;
string str2 = " abc " ;
string str2 = " abc " ;
当我们用System.Object.Equals(str1,str2)比较时,返回值是true;按理说str1和str2应该指向不同的空间,应该返回false才对啊。原来Equals有三个版本:
public
override
bool
Equals(
object
);
public bool Equals( string );
public static bool Equals( string , string );
public bool Equals( string );
public static bool Equals( string , string );
前两个实例方法内部会调用CompareOrdinal静态方法,它会字符串中的各个字符,如果相等就返回true。第三个首先会检查两个引用指向的是否是同一个对象,如果是,就返回true,不再去比较各个字符了。
其实CLR使用了一种叫字符串驻留的技术,对于
string
str1
=
"
abc
"
;
string str2 = " abc " ;
string str2 = " abc " ;
当CLR初始化时,会创建一个内部的散列表,其中的键为字符串,值为指向托管堆中字符串的引用。刚开始,散列表为空,JIT编译器编译方法时,会在散列表中查找每一个文本常量字符串,首先会查找"abc"字符串,并且因为没有找到,编译器会在托管堆中构造一个新的指向"abc"的String对象引用,然后将"abc"字符串和指向该对象的引用添加到散列表中。
接着,在散列表中查找第二个"abc",这一次由于找到了该字符串,所以编译器不会执行任何操作,代码中再没有其它的文本常量字符串,编译器的任务完成,代码开始执行。执行时,CLR发现第一个语句需要一个"abc"字符串引用,于是,CLR会在内部的散列表中查找"abc",并且会找到,这样指向先前创建的String对象的引用就被保存在变量s1中,执行第二条语句时,CLR会再一次在散列表中查找"abc",并且仍然会找到,指向同一个String 对象的引用会被保存在变量s2中,到此s1和s2指向了同一个引用,所以System.Object.Equals(s1,s2)就会返回true了。
另外,C#中是不允许用new操作符创建String对象的,编译器会报错。