为什么String.Empty不是常量?

211
在.NET中,为什么String.Empty是只读的而不是常量?我想知道背后的决策原因。

7
这个问题的答案在这里可以找到,简而言之,没有人知道…… - gdoron
是的,Eric Lippert 的回复很赞,谢谢! - travis
1
特别是考虑到 Decimal.Zero 是常量(从用户的角度来看...) - Hamish Grubijan
4个回答

165
使用static readonly而非const的原因是因为它与非托管代码一起使用,正如Microsoft在Shared Source Common Language Infrastructure 2.0 Release中所述。要查看的文件是sscli20\clr\src\bcl\system\string.cs

Empty常量保存空字符串值。我们需要调用String构造函数,以便编译器不将其标记为文字。

将其标记为文字意味着它不会显示为一个字段,我们无法从本机访问它。

我从CodeProject上这篇有用的文章中找到了这些信息。

1
如果您能解释一下这个注释就太好了(因为Jon Skeet也不能...)请参见:https://dev59.com/Ol7Va4cB1Zd3GeqPHSwO,我将不胜感激。 - gdoron
2
@gdoron:我的猜测(仅仅是猜测)是这样的。当一个值被定义为字面量(常量)时,它的值会被插入到引用它的地方,而当它没有被定义为字面量时,它的值来源会被引用,并在运行时检索实际值。我怀疑后者可能会确保在本机和.NET之间正确地进行字符串编组 - 如果它是字面量,也许本机编译器需要以某种方式将字面值拉入其本机代码中,这可能是不可行的。尽管如此,这只是我的猜测。 - Jeff Yates
7
在方法的默认参数值中,必须使用""而不是string.Empty。这有点令人烦恼。 - nicodemus13
18
“” 可能看起来像是错误,而 string.Empty 则显示有意为之。 - Christopher Stevenson
5
@JeffYates 我想补充一点,不一致的事实已经很烦人了。人们会看到代码的其他部分,然后想知道“为什么他在这里使用""而不是String.Empty?”因为这个原因,我正在认真考虑不再使用String.Empty - julealgon

28

我认为这里存在很多混乱和错误的回答。

首先,const字段是static成员(不是实例成员)。

请查看C#语言规范的第10.4节“常量”。

尽管常量被视为静态成员,但常量声明既不需要也不允许静态修饰符。

如果public const成员是静态的,则不能认为常量将创建一个新对象。

鉴于此,以下代码行在创建新对象方面完全相同

public static readonly string Empty = "";
public const string Empty = "";

这里有一份来自微软的说明,解释了两者之间的区别:

readonly关键字和const关键字不同。const字段只能在声明时初始化。readonly字段可以在声明或构造函数中初始化。因此,readonly字段可以根据使用的构造函数而有不同的值。另外,虽然const字段是编译时常量,但readonly字段可用于运行时常量...

因此,我认为这里唯一合理的答案是Jeff Yates的。


感谢您的赞美和对C#规范中const和static readonly的澄清。 - Jeff Yates
25
重新阅读后,我不同意 const stringstatic readonly string 具有相同的功能。const 常量在链接时被替换,而 static readonly 常量则被引用。如果在库 A 中使用了 const ,并且该常量被库 B 使用,那么库 B 将会用其字面值替换对该常量变量的所有引用;如果该变量是 static readonly ,那么它将被引用,并且其值将在运行时确定。 - Jeff Yates
7
当涉及到库时,杰夫的观点非常重要。如果重新编译 A 并重新分发它,而没有重新编译 B,那么 B 仍将使用旧值。 - Mark Sowul

7
String.Empty read only instead of a constant?

如果您将任何字符串常量化,那么编译器将在您调用它的所有地方替换为实际字符串,并且您的代码中到处都充斥着相同的字符串。当代码运行时,还需要从不同的内存数据中再次读取该字符串。

如果您像String.Empty一样将字符串只读在一个位置上,则程序将仅在一个位置保留相同的字符串并对其进行读取或引用,从而将数据保留在内存中最小化。

此外,如果您使用String.Empty作为const编译任何dll,并且由于任何原因String.Empty更改,则编译的dll将不再以相同的方式工作,因为cost会使内部代码实际上在每次调用时都保留该字符串的副本。

例如,请参见以下代码:

public class OneName
{
    const string cConst = "constant string";
    static string cStatic = "static string";
    readonly string cReadOnly = "read only string";

    protected void Fun()
    {
        string cAddThemAll ;

        cAddThemAll = cConst;
        cAddThemAll = cStatic ;
        cAddThemAll = cReadOnly;    
    }
}

编译器将把它转换为:

public class OneName
{
    // note that the const exist also here !
    private const string cConst = "constant string";
    private readonly string cReadOnly;
    private static string cStatic;

    static OneName()
    {
        cStatic = "static string";
    }

    public OneName()
    {
        this.cReadOnly = "read only string";
    }

    protected void Fun()
    {
        string cAddThemAll ;

        // look here, will replace the const string everywhere is finds it.
        cAddThemAll = "constant string";
        cAddThemAll = cStatic;
        // but the read only will only get it from "one place".
        cAddThemAll = this.cReadOnly;

    }
}

和汇编调用

        cAddThemAll = cConst;
0000003e  mov         eax,dword ptr ds:[09379C0Ch] 
00000044  mov         dword ptr [ebp-44h],eax 
        cAddThemAll = cStatic ;
00000047  mov         eax,dword ptr ds:[094E8C44h] 
0000004c  mov         dword ptr [ebp-44h],eax 
        cAddThemAll = cReadOnly;
0000004f  mov         eax,dword ptr [ebp-3Ch] 
00000052  mov         eax,dword ptr [eax+0000017Ch] 
00000058  mov         dword ptr [ebp-44h],eax 

编辑:纠正错别字


那么,这意味着const字符串必须始终与包含该const的类一起实例化吗?看起来最好使用static readonly。 - the berserker
1
那么编译后的dll就不能再像以前那样工作了,因为成本使得内部代码在每次调用时实际上会保留字符串的副本。@Aristos,您说得不太对。一旦代码被编译,该“副本”将被引用到可执行文件的TEXT块中,并且所有代码只会引用该内存块。你在第二步中引用的只是一个中间步骤。 - Peter Dolkens
@user1533523,谢谢你的留言 - 我会在找到时间时进行测试以检查这个问题。 - Aristos
@jv110 用调试器在那里停止(断点),并要求显示汇编代码。 - Aristos
@PeterDolkens是正确的。编译器将在整个代码中使用相同的实例。您可以执行非常简单的ReferenceEquals()检查以查看这一点。 - aaronburro
显示剩余3条评论

-1

此答案仅供历史参考。

原文:

因为String是一个类,因此不能作为常量。

扩展讨论:

在审核此答案时,进行了很多有用的对话,而不是删除它,直接复制了这些内容:

在.NET中,(与Java不同)string和String完全相同。是的,在.NET中你可以有字符串字面常量。- DrJokepu 2009年2月3日16:57
你是说一个类不能有常量吗?- StingyJack 2009年2月3日16:58
是的,对象必须使用readonly。只有结构体可以使用常量。我认为当你使用string而不是String时,编译器会自动将const更改为readonly。这一切都与让C程序员感到满意有关。- Garry Shutler 2009年2月3日16:59
tvanfosson刚刚解释得更详细了一点。"X不能是常量,因为包含Y的是一个类"只是有点脱离上下文;) - Leonidas 2009年2月3日17:01
string.Empty是静态属性,返回String类的一个实例,即空字符串,而不是字符串类本身。- tvanfosson 2009年2月3日17:01
Empty是String类的只读实例(它不是属性)。- senfo 2009年2月3日17:02
头疼。我仍然认为我是对的,但现在我不太确定了。今晚需要研究!- Garry Shutler 2009年2月3日17:07
空字符串是字符串类的一个实例。Empty是String类的静态字段(不是属性,我纠正了),基本上是指针和它所指向的东西之间的区别。如果它不是只读的,我们可以更改Empty字段所引用的实例。- tvanfosson 2009年2月3日17:07
Garry,你不需要做任何研究。想一想。String是一个类。Empty是String的一个实例。- senfo 2009年2月3日17:12
有一件事我不太明白:String类的静态构造函数如何创建String类的实例?这不是某种“先有鸡还是先有蛋”的情况吗?- DrJokepu 2009年2月3日17:12
除了System.String以外,这个答案对于几乎任何其他类都是正确的。.NET为字符串执行了许多性能特殊处理,其中之一是您可以拥有字符串常量,只需尝试即可。在这种情况下,Jeff Yates给出了正确的答案。- Joel Mueller 2009年2月3日19:25
如§7.18所述,常量表达式是可以在编译时完全评估的表达式。由于除了应用new运算符之外,没有其他方法可以创建非空引用类型的值,而且由于new运算符在常量表达式中不允许使用,因此除了null之外,引用类型的常量的唯一可能值是null。前两个评论直接摘自C#语言规范,并重申了Joel Mueller所提到的内容。- senfo 2009年2月4日15:05

string.Empty 是一个静态属性,它返回 String 类的一个实例,具体是表示空字符串,而不是 String 类本身。 - tvanfosson
Garry,你不需要做任何研究。想一想,String是一个类,Empty是String的一个实例。 - senfo
11
这个答案对于几乎任何类都是正确的,但不适用于 System.String。.NET 为字符串执行了许多性能特殊处理之一就是你可以使用字符串常量,你可以试试看。在这种情况下,Jeff Yates 的答案是正确的。 - Joel Mueller
7
我本来想删除这个回答,因为一个更好的答案已经出现了,但是这些评论中的讨论还值得保留。 - Garry Shutler
1
@Garry,你很幸运,我读了你最后的评论,否则我也会点踩。在.NET中,字符串有一个特殊的特性,即使它是一个ref类也可以成为常量。 - Shimmy Weitzhandler
显示剩余12条评论

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