为什么具有相同内容的两个不同字符串变量具有相同的引用?

3
var str1 = "C#";
var str2 = "F#";    
var str3 = "C#";

Console.WriteLine(Object.ReferenceEquals(str1, str2)); // False
Console.WriteLine(Object.ReferenceEquals(str1, str3)); // True <-- Why?

我知道字符串是不可变的引用类型,这意味着对象不能被改变。但这并不能解释为什么两个字符串引用同一个对象。


3
C# 的编译器会花费额外的工作来使用户代码中出现的字符串实现内部化。在运行时加载时,它们会引用 dll 中相同的字符串。 - JL0PD
1
从规范上来说:公共语言基础设施(CLI)保证了两个引用具有相同字符序列的元数据标记的ldstr指令的结果返回完全相同的字符串对象(这个过程称为“字符串内联”)。 - JL0PD
1
在您的代码中,"C#"F#是编译时常量,其引用被分配给变量str1str2str3。由于字符串是不可变的,编译器可以创建每个字符串的一个共享实例(存储在程序集本身中),并将对它的引用传递给所有需要它的变量。 - Panagiotis Kanavos
2个回答

5
对于字面量(即出现在代码中的字符串)和其他常量字符串值(即可以在编译时完全计算出来的字符串操作的结果),编译器使用ldstr操作,它会加载一个已经被共享的字符串实例 - 基于代码中字面量通常会被大量重用,因此不断分配新的字符串实例是不划算的。因此它们具有相同的引用。引用自ldstr文档:

通用语言基础设施(CLI)保证两个引用相同元数据标记的 ldstr 指令返回确切相同的字符串对象(这个过程称为“字符串内联”)。

由于字符串实例是不可变的,所以这样做没问题(至少理论上如此;实际上,人们总是可以通过不安全的代码等方式改变不可变数据,但这属于“玩蠢游戏,赢取蠢结果”的范畴)。

如果您从char[]加载这些值,或者从byte[]解码,或者作为字符串操作的结果等:它们将是不同的字符串,只是恰好具有相同的内容。

3

这被称为字符串驻留。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

另请参阅:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接