var str1 = "C#";
var str2 = "F#";
var str3 = "C#";
Console.WriteLine(Object.ReferenceEquals(str1, str2)); // False
Console.WriteLine(Object.ReferenceEquals(str1, str3)); // True <-- Why?
我知道字符串是不可变的引用类型,这意味着对象不能被改变。但这并不能解释为什么两个字符串引用同一个对象。
var str1 = "C#";
var str2 = "F#";
var str3 = "C#";
Console.WriteLine(Object.ReferenceEquals(str1, str2)); // False
Console.WriteLine(Object.ReferenceEquals(str1, str3)); // True <-- Why?
我知道字符串是不可变的引用类型,这意味着对象不能被改变。但这并不能解释为什么两个字符串引用同一个对象。
ldstr
操作,它会加载一个已经被共享的字符串实例 - 基于代码中字面量通常会被大量重用,因此不断分配新的字符串实例是不划算的。因此它们具有相同的引用。引用自ldstr
文档:
通用语言基础设施(CLI)保证两个引用相同元数据标记的 ldstr 指令返回确切相同的字符串对象(这个过程称为“字符串内联”)。
由于字符串实例是不可变的,所以这样做没问题(至少理论上如此;实际上,人们总是可以通过不安全的代码等方式改变不可变数据,但这属于“玩蠢游戏,赢取蠢结果”的范畴)。
如果您从char[]
加载这些值,或者从byte[]
解码,或者作为字符串操作的结果等:它们将是不同的字符串,只是恰好具有相同的内容。这被称为字符串驻留。C#编译器将会对字符串字面量进行驻留,即利用字符串的不可变性,将它们添加到程序集中的特殊存储空间中,使得相同的字符串字面量引用相同的实例,从而节省空间。考虑下面的例子:
var str1 = "C#";
var str2 = "F#";
var str3 = "C#";
var str4 = String.Concat("C", "#");
Console.WriteLine(Object.ReferenceEquals(str1, str2)); // False
Console.WriteLine(Object.ReferenceEquals(str1, str3)); // True
Console.WriteLine(Object.ReferenceEquals(str1, str4)); // False
由于str4
是动态构建的,因此不会被存储在池中,将会创建新的字符串实例,因此返回False
(尽管str1.Equals(str4)
返回True
)
另请参阅:
int.ToString()
,导致类似Object.ReferenceEquals("1".ToString(),"1")
的结果为True
ldstr
指令的结果返回完全相同的字符串对象(这个过程称为“字符串内联”)。 - JL0PD"C#
和"F#
是编译时常量,其引用被分配给变量str1
、str2
和str3
。由于字符串是不可变的,编译器可以创建每个字符串的一个共享实例(存储在程序集本身中),并将对它的引用传递给所有需要它的变量。 - Panagiotis Kanavos