字符串的行为已更改.在.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新版本的JIT,它显然通过内联对特定String
实例的引用来优化对String.Empty
的引用,而不是加载存储在Empty
字段中的值。根据ECMA-335分区I§8.6.1.2中init only约束的定义,这是合理的,可以解释为String
类初始化后String.Empty
字段的值不会改变。
我没有答案,也许只是一些提示。
我看到的String::Empty
和System.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有什么含义