42

精简版:

C# 代码

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

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

如何像这样忽略对字段的写入,或者,谁重置了这个字段?

更长的版本:

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

  • switch表格的声明中case string.Empty:
  • 作为可选参数的默认值,例如void M(string x = string.Empty) { }
  • 应用属性时,例如[SomeAttribute(string.Empty)]
  • 需要编译时常量的其他情况

这对是否使用string.Emptyor的众所周知的“宗教战争”有影响"",请参阅“在 C# 中,我应该使用 string.Empty 或 String.Empty 还是“”来初始化字符串? ”。

几年前,我Empty通过反射设置到其他字符串实例来自娱自乐,看看 BCL 有多少部分因此而开始表现出奇怪的行为。这是相当多的。并且Empty引用的更改似乎在应用程序的整个生命周期中持续存在。现在,前几天我试图重复那个小特技,但后来使用了 .NET 4.5 机器,我再也做不到了。

(注意!如果您的机器上有 .NET 4.5,可能您PowerShell仍然使用旧版本的 .NET(编辑:仅适用于 PowerShell 未更新到 PowerShell 2.0 之后的 Windows 7 或更早版本),因此请尝试复制粘贴[String].GetField("Empty").SetValue($null, "Hello world!")到PowerShell 查看更改此参考的一些效果。)

当我试图寻找原因时,我偶然发现了一个有趣的线程“ .NET 4.5 beta 中这个 FatalExecutionEngineError 的原因是什么? ”。在该问题的公认答案中,是否注意到通过版本 4.0,System.String有一个静态构造函数.cctor,其中设置了字段Empty(在 C# 源代码中,这可能只是一个字段初始值设定项,当然),而在 4.5 中没有静态构造函数存在。在这两个版本中,字段本身看起来都一样:

.field public static initonly string Empty

(如 IL DASM 所示)。

String::Empty似乎没有其他领域受到影响。例如,我尝试了System.Diagnostics.Debugger::DefaultCategory. static readonly这种情况看起来很相似:一个包含类型为( static initonly)的密封类string。但在这种情况下,通过反射更改值(参考)可以正常工作。

回到问题:

当我设置字段时,从技术上讲,这怎么可能Empty没有改变(在 4.5 中)?我已经验证 C# 编译器不会“欺骗”读取,它输出 IL 如下:

ldsfld     string [mscorlib]System.String::Empty

所以应该阅读实际的字段。


在对我的问题提出赏金后进行编辑:请注意,写入操作(肯定需要反射,因为该字段是readonly(又名initonly在 IL 中))实际上按预期工作。异常的是操作。如果您通过反射阅读,如 中typeof(string).GetField("Empty").GetValue(null),一切正常(即看到值的变化)。请参阅下面的评论。

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

4

3 回答 3

22

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

于 2013-11-13T22:23:10.000 回答
3

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

String::Empty我看到和之间的唯一区别System.Diagnostics.Debugger::DefaultCategory是第一个带有__DynamicallyInvokableAttribute.

我不知道这个未记录的属性的含义。在 SO 上提出了一个关于此属性的问题:__DynamicallyInvokable 属性是干什么用的?

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

于 2013-05-19T02:01:03.007 回答
2

因为它可以。

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

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

有关在运行时和影响之间变化的优化的更多信息,请参阅答案

于 2013-11-14T17:09:08.423 回答