字符串驻留。编译器如何知道?

6
我知道字符串驻留是什么,以及为什么以下代码的行为是这样的:
var hello = "Hello";
var he_llo = "He" + "llo";
var b = ReferenceEquals(hello, he_llo); //true

或者

var hello = "Hello";
var h_e_l_l_o = new string(new char[] { 'H', 'e', 'l', 'l', 'o' });
var b = ReferenceEquals(hello, he_llo); //false

我以为我知道怎么做,因为在我正在处理的某些代码中出现了一个微妙的错误,原因是这个:

var s = "";
var sss = new string(new char[] { });
var b = ReferenceEquals(s, sss); //True!?

编译器如何“知道”sss实际上将是一个空字符串?

2
因为CLR内部的char[]string构造函数具有特殊逻辑,如果传递一个空数组,它将简单地指向一个真正的空字符串,而不是实际构造一个新对象。在SO上有一个问题(标题很糟糕)解释了这一点。明确一点,这是一个运行时问题--令人惊讶的不是编译器有先见之明,而是new并不总是new - Jeroen Mostert
1
一个有趣的后续问题是:是否有任何方法可以在运行时创建一个空字符串s(使得s.Length == 0),而Object.ReferenceEquals(s, "")不成立?如果有的话,我还没有找到 -- 通过操作一个最初非空的字符串来创建一个空字符串似乎行不通,无论你多聪明。 - Jeroen Mostert
如果你查看已编译->反编译代码,你会发现你所询问的示例是按照原样编译的(请看右边的窗格)。 - xanatos
以下是一些示例代码的小提琴:https://dotnetfiddle.net/xdtcRG - user310988
@JeroenMostert 非常感谢您的所有建议!非常有教益。 - InBetween
显示剩余6条评论
1个回答

4

如果在字符串构造函数中传入一个空数组或null 数组,它将返回一个空字符串。

这在参考代码的注释中有说明。

 // Creates a new string with the characters copied in from ptr. If
 // ptr is null, a 0-length string (like String.Empty) is returned.

您也可以使用 null 数组来获得相同的结果,例如:
char[] tempArray = null;
var s = "";
var sss2 = new string(tempArray);
var b = ReferenceEquals(s, sss2); //True!?

2
你应该明确指出这是在运行时发生的,而不是编译时发生的(与 "A" + "B""AB" 在编译时发生相比)。 - xanatos
抱歉,但这是一个相当肤浅的答案。它创建一个空字符串是显而易见的,但这里微妙的部分不在于返回一个空字符串,而在于始终返回特定的内部化空字符串。在C#中你不能这样做;正如评论中已经提到的,new并没有真正创建新的东西,这非常出人意料。 - InBetween
@InBetween 但是被调用的不是一个“C#”构造函数。如果您查看答案中对参考代码的链接,您会发现该注释位于一个外部互操作定义上,它是一个“MethodImplOptions.InternalCall”。这意味着它正在调用CLR,并且CLR可以返回任何实例。 - user310988
@AndyJ 这个问题的回答里有提到吗? - InBetween
你认为在这个讨论中,“.NET Framework”和CLR有什么不同?CLR是运行时,它实现了newobj指令,从而导致构造函数调用本身。没有任何层次介于其中,做一些“稍后”的事情。说某些版本的CLR表现出这种行为,某些版本则不正确(尽管Eric不幸地没有提到哪些版本不正确,或者是否受运行时设置的影响)。 - Jeroen Mostert
显示剩余2条评论

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