简短版:
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
),请参见“Why isn't String.Empty a constant?”。这意味着,例如在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(编辑:仅适用于未更新到PowerShell 2.0以外的Windows 7或更早版本),因此请尝试将[String].GetField("Empty").SetValue($null, "Hello world!")
复制粘贴到PowerShell中以查看更改此引用的一些效果。)
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
(即IL中的initonly
))实际上按预期工作。 异常的是读取操作。如果使用反射进行读取,例如typeof(string).GetField("Empty").GetValue(null)
,则一切正常(即可以看到值的更改)。请参见下面的评论。
因此,更好的问题是:为什么这个新版本的框架在读取这个特定字段时会欺骗呢?
string.Empty
的值时会发生什么? - Kirk WollConsole.WriteLine(typeof(string).GetField("Empty").GetValue(null));
在4.5中会打印出"Hello world!"
。 - Jeppe Stig Nielsenstring.Empty
”。另一个问题是关于为什么可以在系统程序集中写入readonly
字段;而这个问题则是关于为什么和如何在某些版本的.NET中,特定字段会拒绝某些基于反射的写操作。 - O. R. Mapper