字符串的行为已更改.在.NET 4.5中为空(或System.String::Empty)

本文关键字:System String Empty 字符串 NET | 更新日期: 2023-09-27 18:14:05

短版本:

C#代码

typeof(string).GetField("Empty").SetValue(null, "Hello world!");
Console.WriteLine(string.Empty);

编译和运行时,在.NET 4.0及更早版本下给出输出"Hello world!",但在.NET 4.5和.NET 4.5.1下给出""

怎么能像那样忽略对字段的写入,或者,谁重置了这个字段?

更长版本:

我从来没有真正理解过为什么string.Empty字段(也称为[mscorlib]System.String::Empty(不是const(也称为.literal(,请参阅"为什么String.Empty不是常量?"。这意味着,例如,在C#中,我们不能在以下情况下使用string.Empty

  • 在形式为case string.Empty:switch语句中
  • 作为可选参数的默认值,如void M(string x = string.Empty) { }
  • 应用属性时,如[SomeAttribute(string.Empty)]
  • 需要编译时间常数的其他情况

这与众所周知的关于是使用string.Empty还是""的"宗教战争"有关,请参阅"在C#中,我应该使用string.Empty还是string.Empty或"来初始化字符串?"。

几年前,我通过反射将Empty设置为其他字符串实例,以此自娱自乐,看看BCL中有多少部分因此而开始表现得奇怪。Empty参考值的变化似乎在应用程序的整个生命周期内持续存在。前几天,我试着重复那个小特技,但后来用了一台.NET 4.5机器,我再也做不到了。

(注意!如果您的计算机上有.NET 4.5,则您的PowerShell可能仍使用较旧版本的.NET(EDIT:仅适用于Windows 7或更早版本,其中PowerShell在PowerShell 2.0之后尚未更新(,因此请尝试将[String].GetField("Empty").SetValue($null, "Hello world!")复制粘贴到PowerShell中,以查看更改此引用的一些效果。(

当我试图寻找原因时,我偶然发现了一个有趣的线程"是什么原因导致了.NET 4.5测试版中的致命执行引擎错误?"。在这个问题的公认答案中,是否注意到,在4.0版本中,System.String有一个静态构造函数.cctor,其中设置了字段Empty(当然,在C#源中,这可能只是一个字段初始化器(,而在4.5版本中不存在静态构造函数。在这两个版本中,字段本身看起来是一样的:

.field public static initonly string Empty

(如IL DASM所示(。

除了String::Empty之外,似乎没有其他字段受到影响。作为一个例子,我用System.Diagnostics.Debugger::DefaultCategory进行了实验。这种情况看起来类似:一个包含string类型的static readonly(static initonly(字段的密封类。但在这种情况下,通过反射来更改值(引用(是很好的。

返回问题:

从技术上讲,当我设置字段时,Empty似乎没有改变(在4.5中(,这怎么可能呢?我已经验证了C#编译器在读取时不会"作弊",它会输出类似IL的:

ldsfld     string [mscorlib]System.String::Empty

所以应该读取实际字段。


在我的问题被提出后编辑:请注意,写操作(当然需要反思,因为字段是readonly(在IL中也称为initonly((实际上按预期工作。异常的是read操作。如果你带着反思阅读,就像在typeof(string).GetField("Empty").GetValue(null)中一样,一切都是正常的(即可以看到值的变化(。请参阅下面的评论。

所以更好的问题是:为什么这个新版本的框架在读取这个特定字段时会作弊?

字符串的行为已更改.在.NET 4.5中为空(或System.String::Empty)

不同之处在于.NET新版本的JIT,它显然通过内联对特定String实例的引用来优化对String.Empty的引用,而不是加载存储在Empty字段中的值。根据ECMA-335分区I§8.6.1.2中init only约束的定义,这是合理的,可以解释为String类初始化后String.Empty字段的值不会改变。

我没有答案,也许只是一些提示。

我看到的String::EmptySystem.Diagnostics.Debugger::DefaultCategory之间的唯一区别是第一个用__DynamicallyInvokableAttribute标记。

我不知道这个未记录属性的含义。SO上有人问了一个关于此属性的问题:__DynamicallyInvokeable属性用于什么?

我只能假设这个属性被运行时捕获以进行一些缓存?

因为它可以。

这些系统定义的initonly字段的值是.NET运行时的全局不变量。如果这些不变量被破坏,就不再有任何关于行为的保证。

在C++中,我们可能有一个规则,将其指定为导致未定义的行为。在.NET中,它也是未定义的行为,只是因为没有任何规则说明System.String.Empty.Length > 0时会发生什么。NET和C#的所有层的整个规范描述了当System.String.Empty.Length == 0和一大堆不变量也成立时的行为。

有关优化的更多信息,这些优化在运行时和含义之间有所不同,请参阅的答案

  • 要求反射API覆盖System.String.Empty有什么含义
相关文章: