在.NET 4.5中更改了string.Empty(或System.String :: Empty)的行为

简洁版本:

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 (又名literal ),请参阅“ 为什么不是String.Empty常量? ”。 这意味着,例如,在C#中我们不能在以下情况下使用string.Empty

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

这对有关是否使用string.Empty""的众所周知的“宗教战争”有影响,请参阅“ 在C#中,我应该使用string.Empty还是String.Empty或”“来intitialize一个string?

几年前,我通过反思将Empty设置为其他string实例,并且看到BCL中有多less部分开始因为它而奇怪地行为。 这是相当多的。 Empty引用的变化似乎在应用程序的整个生命周期中一直存在。 现在,有一天我试着重复那个小小的特技,但是后来使用了一个.NET 4.5的机器,我不能再这样做了。

(注意!如果你的机器上有.NET 4.5,可能你的PowerShell仍然使用旧版本的.NET,所以试试copy- [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 。 这种情况似乎是类似的:一个密封的类,包含一个types为stringstatic readonlystatic initonly )字段。 但是在这种情况下,通过reflection来改变值(参考)是可行的。

回到问题:

在技​​术上,怎么可能在设置领域的时候Empty似乎没有改变(4.5)? 我已经validation了C#编译器不会“读取”作弊,它会输出如下所示的IL:

 ldsfld string [mscorlib]System.String::Empty 

所以实际的领域应该被读取。


编辑后赏金放在我的问题:请注意,写操作(它需要肯定的reflection,因为该字段是readonly (又名initonly在IL))实际上按预期工作。 这是exception的读取操作。 如果用reflection读取,就像在typeof(string).GetField("Empty").GetValue(null) ,一切都是正常的(即可以看到值的变化)。 见下面的评论。

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

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

也许我没有答案,只是暗示。

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

我不知道这个无证的属性的含义。 关于这个属性的问题已经在SO上被问到: 什么是__DynamicallyInvokable属性?

我只能假设这个属性被运行时捕获到做一些caching?

因为可以。

这些系统定义的initonly字段的值是.NET运行时的全局不variables。 如果这些不variables被打破了,那么就不再有任何行为的保证

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

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

  • 请求Reflection API重写System.String.Empty有什么含义?