为什么String.Empty不是常量?


188

在.Net中,为什么String.Empty只读而不是常量?我只是想知道是否有人知道该决定背后的原因。


5
这个问题可以解决这个问题,简单的答案是,没人知道...
gdoron支持Monica 2011年

是的,Eric Lippert的回应+1,谢谢!
travis

特别是考虑到Decimal.Zero是const(从用户角度来说是...)
Hamish Grubijan 2012年

Answers:


147

static readonly代替constMicrosoft 的原因是由于与非托管代码一起使用,如Microsoft在“ 共享源通用语言基础结构2.0版本”中所示。要查看的文件是sscli20\clr\src\bcl\system\string.cs

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

将其标记为文字将意味着它不会显示为我们可以从本地访问的字段。

在CodeProject的这篇便利文章中找到了此信息。


我真的很感激,如果你能解释一下这条评论(因为乔恩斯基特不能...)在这里看到:stackoverflow.com/questions/8462697/...
gdoron正在支持莫妮卡

2
@gdoron:我的猜测是(这是一个猜测)。如果将值定义为文字(常数),则将其值插入引用的位置,而如果未将其定义为文字,则将引用值的源,并在运行时检索实际值。我怀疑后者可能确保在运行时在本机和.NET之间进行适当的字符串编组-如果是文字,则本机编译器可能需要以某种方式将文字值拉入其本机代码中,这可能不是可行。不过,这全都是我的推测。
杰夫·耶茨

7
这确实意味着必须使用“”而不是string.Empty作为方法中的默认参数值。这有点烦人。
nicodemus13 2012年

17
“”看起来像是一个错误,而string.Empty显示了故意的意图
Christopher Stevenson

3
@JeffYates我要补充一点,即不一致的事实已经很烦人了。人们会看到其余的代码,并且想知道“他为什么在这里使用“”而不是String.Empty?”。我正在认真考虑String.Empty不再仅出于这个原因不再使用它。
julealgon

24

我认为这里有很多混乱和不良反应。

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

检查C#语言规范的10.4常量。

即使常量被认为是静态成员,常量声明也不需要也不允许使用静态修饰符。

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

鉴于此,以下代码行在创建新Object方面的作用完全相同

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

这是Microsoft的注释,解释了2之间的区别:

readonly关键字与const关键字不同。const字段只能在该字段的声明中初始化。只读字段可以在声明中或在构造函数中初始化。因此,只读字段可以具有不同的值,具体取决于所使用的构造函数。同样,虽然const字段是编译时常量,但readonly字段可用于运行时常量,...

因此,我发现这里唯一合理的答案是杰夫·耶茨(Jeff Yates)。


+1表示友好的单词,并说明有关const和静态只读的C#规范。
杰夫·耶茨(

17
重新阅读此内容,我不同意const stringstatic readonly string做同样的事情。常量值在链接的代码中被替换,而静态只读值被引用。如果您有一个const库B使用的库A,则库B会将所有对该const变量的引用替换为其文字值;如果static readonly改为使用该变量,则将在运行时对其进行引用并确定其值。
杰夫·耶茨

3
引用库时,Jeff的观点很重要。如果您重新编译A并重新分配它,而不重新编译B,则B仍将使用旧值。
Mark Sowul

4
String.Empty read only instead of a constant?

如果使任何字符串常量,则编译器将在调用该字符串的任何地方都替换为实际的字符串,并用相同的字符串填充代码,并且在代码运行时还需要从不同的内存中一次又一次地读取该字符串数据。

如果您将字符串仅保留在一个位置上,则String.Empty该程序仅将一个字符串保留在一个位置上并对其进行读取或引用,以使数据在内存中的存储量最小。

另外,如果您使用String.Empty作为const编译任何dll,并且由于任何原因更改了String.Empty,则编译后的dll将不再起作用,因为costmake内部代码实际上会保留字符串的副本在每个电话上。

例如,请参见以下代码:

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字符串吗?看来使用静态只读更好。
疯子

@theberserker更好,但是您可以使用所有选项。
亚里斯多斯

>然后,编译后的dll将不再起作用,因为代价是内部代码实际上会在每次调用时保留字符串的副本。@Aristos不太正确。编译代码后,将在可执行文件的TEXT块中引用该字符串的“副本”,并且所有代码都将仅引用该相同的内存块。您在第二步中引用的内容只是一个中间步骤。
Peter Dolkens '16

@ user1533523感谢您的注释-我会在有时间检查该问题时进行测试
Aristos

您是如何获得该汇编代码的?C#无法编译为汇编!
jv110

0

存在此答案是出于历史目的。

本来:

因为String是一个类,所以不能是常量。

扩展讨论:

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

在.NET中,(与Java不同)string和String完全相同。是的,您可以在.NET中拥有字符串文字常量– DrJokepu '09 Feb 3'在16:57

您是在说一个类不能有常量吗?– StingyJack 09年2月3日在16:58

是的,对象必须使用只读。只有结构体可以做常量。我认为,当您使用string而不是String编译器时,会将const更改为只读。所有与让C程序员感到高兴有关。–加里·舒特勒(Garry Shutler)'2009年2月3日在16:59

tvanfosson只是稍微解释了一下。“ X不能为常数,因为包含的Y是一个类”有点上下文无关;)– Leonidas 09年2月3日在17:01

string.Empty是静态属性,它返回String类的实例,即空字符串,而不是字符串类本身。– tvanfosson 09年2月3日在17:01

空是String类的只读实例(不是属性)。– SENFO 09年2月3日在17:02

头好痛 我仍然认为我是对的,但现在我不太确定了。今晚需要研究!–加里·舒特勒(Garry Shutler)2009年2月3日在17:07

空字符串是字符串类的实例。空是String类上的静态字段(不是我所纠正的属性)。基本上,指针与其指向的东西之间的区别。如果不是只读的,我们可以更改Empty字段引用的实例。– tvanfosson 09年2月3日在17:07

加里,你不需要做任何研究。想一想。字符串是一个类。空是字符串的实例。– SENFO 09年2月3日在17:12

我还不太了解:String类的静态构造函数如何创建String类的实例?那不是某种“鸡还是蛋”的场景?– DrJokepu '09年2月3日在17:12 5

对于System.String以外的几乎所有其他类,此答案都是正确的。.NET对字符串做了很多性能上的特殊修饰,其中之一是您可以拥有字符串常量,只需尝试一下即可。在这种情况下,Jeff Yates的答案正确。–乔尔·穆勒(Joel Mueller),09年2月3日,19:25

如第7.18节所述,常量表达式是可以在编译时完全求值的表达式。由于创建除字符串以外的引用类型的非空值的唯一方法是应用new运算符,并且由于常量表达式中不允许使用新运算符,因此引用类型的常量唯一可能的值字符串以外的其他为空。前两个注释直接取自C#语言规范,并重申了Joel Mueller提到的内容。– SENFO 09年2月4日15:05 5


请拒绝正确答案。如果您转到“定义”,则会发现它位于String类上,并且是String的实例。它显示为小写的事实是编译器不可思议的。
加里·舒特勒

不是我对您投了反对票,但在.NET中(与Java不同),string和String完全相同。是的,您可以在.NET中拥有字符串文字常量
Tamas Czinege,2009年

10
对于System.String以外的几乎所有其他类,此答案都是正确的。.NET对字符串做了很多性能上的特殊修饰,其中之一是您可以拥有字符串常量,只需尝试一下即可。在这种情况下,Jeff Yates的答案正确。
乔尔·穆勒

7
由于有了更好的答案,我几乎删除了这个答案,但是这些评论中的讨论值得保留。
加里·舒特勒

1
@Garry,您很幸运,我阅读了您的最后评论,否则我也会投反对票。字符串在.NET中具有特殊功能,因此它是ref类,可以是const。
Shimmy Weitzhandler
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.