".NET/BCL源代码中上面的混淆注释"string.Empty"是什么意思?"

35
我正在尝试理解为什么string.Emptyreadonly而不是const。我看到这个帖子,但我不理解Microsoft在评论中写的内容。正如Jon Skeet在评论中所写的那样,"我不知道 - 老实说,这对我来说没有多少意义......"。 共享源代码基础结构2.0发布。string.cs位于sscli20 \ clr \ src \ bcl \ system \ string.cs中。
// The Empty constant holds the empty string value.
//We need to call the String constructor so that the compiler doesn't mark this as a literal.
//Marking this as a literal would mean that it doesn't show up as a field which we can access 
//from native.
public static readonly String Empty = ""; 

我在这里看不到任何String构造函数的调用,而且它被标记为字面量 - ""

能否有人用简单的语言解释一下这个注释的意思,以及为什么string.Empty是只读的而不是常量?


更新:
Eric Lippert 在一个已删除的答案中发表了评论:(链接)

我在午餐时问了一位C#老手关于这个问题,他不记得具体为什么做出了这个决定,但猜测可能与内部化有关。


9
也许这是其中一条曾经有意义的评论,但随着代码的更改,它不再有意义了......不是我做过这种事情或什么的......(他走开时吹着口哨)。 - user166390
49
“为什么它不是一个常量” - 因为如果是常量,你就不能使用 typeof(string).GetField("Empty").SetValue(null, null); 或者 typeof(string).GetField("Empty").SetValue(null, " "); 进行调试了,这太无趣了!嘿嘿嘿;嘿嘿嘿嘿嘿嘿嘿;嘿嘿嘿嘿嘿嘿!! - Marc Gravell
4
@MarcGravell,当需要Eric Lippert时他在哪里?“帮帮我超人……” - gdoron
12
如果Jon Skeet都不明白,那你就注定要失败了。 - Bojan Kogoj
8
根据评论,似乎在过去的某个时期中,常量没有被视为字段。 - Raymond Chen
1
非常有趣(也很悲哀)的是,提供该评论答案的回答被接受并获得了49个赞,但没有一个包括CLR团队的人理解它。 - gdoron
2个回答

16
重要的部分并不是这个类里面发生了什么,而是当另一个类使用它(并且链接到它)时会发生什么。我用另一个例子来解释一下:
假设你有一个包含声明类的Assembly1.dll。
public static const int SOME_ERROR_CODE=0x10;
public static readonly int SOME_OTHER_ERROR_CODE=0x20;

还有另一个使用该类的类,例如:

public int TryFoo() {
    try {foo();}
    catch (InvalidParameterException) {return SOME_ERROR_CODE;}
    catch (Exception) { return SOME_OTHER_ERROR_CODE;}
    return 0x00;
}

你将类编译为 Assembly2.dll 并将其与 Assembly1.dll 进行链接,如预期的一样,如果参数无效,则方法将返回 0x10,在其他错误情况下返回 0x20,在成功时返回 0x00。

特别地,如果你创建了包含以下内容的 Assembly3.exe:

int errorcode=TryFoo();
if (errorcode==SOME_ERROR_CODE) bar();
else if (errorcode==SOME_OTHER_ERROR_CODE) baz();

在链接Assembly1.dll和Assembly2.dll后,它将按预期工作。

现在,如果你得到了一个新版本的Assembly1.dll,其中

public const int SOME_ERROR_CODE=0x11;
public readonly int SOME_OTHER_ERROR_CODE=0x21;
如果重新编译Assembly3.exe并将最后一个片段与新的Assembly1.dll和未更改的Assembly2.dll链接,它将无法按预期工作:
bar()将不会被正确调用:Assembly2.dll记住了文字0x20,这与Assembly3.exe从Assembly1.dll中读取的0x21不同。
baz()将被正确调用:Both Assembly2.dll and Assembly3.exe refer to the SYMBOL REFERENCE called SOME_OTHER_ERROR_CODE, which is in both cases resolved by the current version of Assembly1.dll, thus in both cases is 0x21.
简而言之:const创建LITERALreadonly创建SYMBOL REFERENCELITERAL是内部框架的,不能被封送处理,因此不能被本地代码使用。
所以,
public static readonly String Empty = ""; 

创建一个符号引用(在首次使用时通过调用String构造函数进行解析),可以从本地进行编排并使用,同时

public static const String Empty = ""; 

会创建一个字面量,但有些情况下可能不行。


3
string.Empty的好处在于它不会被修改,而且其值不会被记忆。使用const关键字也是可以的,你觉得如何? - gdoron
1
我怀疑这可能与编组有关:代码注释谈到了能够从本地调用它,并且文字量不可编组。 - Eugen Rieck
2
我想了解为什么字面值不可序列化...字面值和返回相同值的引用有什么不同? - Camilo Martin
3
虽然这个回答正确地解释了为什么要使用“readonly”,但它并没有回答问题。 - Mark Hurd
@MarkHurd,所以OQ由两部分组成:前面我们已经讨论过了。前一部分是“评论的含义是什么”。评论是“不要将其作为文字,因为我们无法从本地访问文字”。我的答案的最后一部分(从“简而言之”开始)谈到了文字是无法解除绑定的,因此在框架外无法使用-我认为这回答了OQ的第一部分。 - Eugen Rieck
显示剩余2条评论

0
这并不是直接回答“为什么”的问题,但它提供了一些附加的上下文信息。运行时和JIT将String.Empty视为内部方法。在底层,它的工作方式与""完全相同。两者都会给您一个指向空字符串单例的指针,并且JIT代码也看起来相同。这只是一个样式问题,无论您使用哪个都没有内存分配差异。

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