字符串驻留和等号运算符

4
我刚刚阅读了这个网站,上面写道:虽然string是一个引用类型,但等号操作符(==和!=)被定义为比较string对象的值,而不是引用...a和b不指向同一个字符串实例 (http://msdn.microsoft.com/en-us/library/362314fe.aspx)。
我没有检查String类的内部情况,但那个说法正确吗?据我所知,String是不可变的原因是由于字符串池。换句话说,每个唯一值只存储一个字符串副本。所有具有相同值的String变量引用同一个对象。我认为这就是为什么"a" == "a"可以工作--不是因为它被定义为比较值。如果它正在检查值,则必须逐个字符比较字符串,这会导致显着的性能问题,并消除使用字符串池的主要原因之一。
也许他们过于简化了,但我认为暗示String等式运算符与其他引用类型定义不同是误导人的。如果我错了,请纠正我!

只有编译时的字符串字面量默认进行内部化,所以如果在运行时创建了多个相同文本的字符串,可能会有多个副本存在。 - Brian Rasmussen
3个回答

4

字符串可以被内部化,但不一定需要。字符串字面量默认情况下是内部化的(现在可以通过CompilationRelaxations.NoStringInterning属性进行更改),在运行时创建的实例可能会被内部化,但通常不会,除非采取特殊步骤(例如调用String.Intern())。

可能存在多个具有相同值的字符串实例。

此外,除了能够内部化字符串之外,它们是不可变的原因还有其他原因 - 不可变性主要是为了使持有引用的对象不必担心这些值在其背后发生变化。因此,更准确地说,能够内部化字符串是不可变性的结果,而不是字符串必须是不可变的,以便我们可以将它们内部化。


有关字符串何时被内部化的任何规则吗?由于字符串可能来自外部系统,因此必须在运行时执行此操作。 - Nelson Rothermel
1
我记得仅有的情况是在运行时使用字符串作为 switch 语句的条件时才会使字符串被 intern。 - Lasse V. Karlsen
@Lasse 为什么要这样做?字符串开关在隐藏的字典映射字符串(来自ldstr)到整数,然后是表跳转。不需要为此将运行时字符串 intern 化。 - Marc Gravell
所以如果你像这样做 string a = GetExternalString(); string b = String.Intern(a);,那么你是在不必要地分配 a 吗?如果 a 的值已经被 interned 了,那么 string b = String.Intern(GetExternalString()) 是否可以防止这种情况? - Nelson Rothermel
@Nelson:除非 GetExternalString() 返回的是已经被内部化的字符串,否则会有一个非内部化的字符串被暂时分配。可以推测,对它的引用很快就会消失(特别是在您最后的示例中),因此它将很快被回收。但确实需要进行回收。 - Michael Burr
显示剩余4条评论

2
并非所有的字符串都是被缓存的; 编译器中的文字/常量(特别是ldstr IL代码)会自动被缓存,你可以手动进行检查缓存 - 但大部分在运行时构造的字符串(例如来自用户输入、从文件/数据库加载或与值连接)都不会。因此,是的:进行值的检查是必要的。
缓存仅仅是一种优化方式;即使没有缓存也需要正常工作。

虽然我认为在运行时它会在分配新字符串之前检查内部列表,但这很有道理。当然,每次连接字符串也会产生性能影响。 - Nelson Rothermel
1
@Nelson,它不会自动执行此操作;仅在明确请求或通过 ldstr 时才执行;大多数字符串是如此短暂(gen0),以至于分配它们并让它们死亡比用大量检查破坏每个分配更便宜。 - Marc Gravell
所以我学到了新东西...这让我想起了一般情况下的“建索引还是不建索引”的问题。建立索引可以加快读取速度,但会减慢写入速度。 - Nelson Rothermel

2
不,文档确切地说明了它的含义:对于String类型,==运算符逐个字符比较字符串的值。仅仅因为一个变量是引用类型并不意味着它必须按照引用进行比较;这就是为什么operator==可以被重载的原因。
通过反编译器的探索,我们发现Stringoperator==(String, String)调用了Equals(String, String),该方法使用了不安全指针来比较底层的值。

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