问题描述
简短版本:
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.
when compiled and run, gives output "Hello world!"
under .NET version 4.0 and earlier, but gives ""
under .NET 4.5 and .NET 4.5.1.
如何像这样忽略对字段的写入,或者谁重置了该字段?
How can a write to a field be ignored like that, or, who resets this field?
更长的版本:
我一直不明白为什么 string.Empty
字段(也称为 [mscorlib]System.String::Empty
)不是 const
code>(又名.literal
),请参阅为什么 String.Empty 不是常量?".这意味着,例如,在 C# 中,我们不能在以下情况下使用 string.Empty
:
I have never really understood why the string.Empty
field (also known as [mscorlib]System.String::Empty
) is not const
(aka. literal
), see "Why isn't String.Empty a constant?". This means that, for example, in C# we can't use string.Empty
in the following situations:
- 在
case string.Empty:
形式的 - 作为可选参数的默认值,如
void M(string x = string.Empty) { }
- 应用属性时,如
[SomeAttribute(string.Empty)]
- 其他需要编译时常量的情况
switch
语句中这对众所周知的宗教战争"是否使用 string.Empty
或 ""
有影响,请参阅在 C# 中,我应该使用 string.Empty 还是 String.Empty 或"来初始化字符串?".
which has implications to the well-known "religious war" over whether to use string.Empty
or ""
, see "In C#, should I use string.Empty or String.Empty or "" to intitialize a string?".
几年前,我通过反射将 Empty
设置为其他一些字符串实例来娱乐自己,看看 BCL 的多少部分因此开始表现异常.这是相当多的.并且 Empty
引用的更改似乎在应用程序的整个生命周期中都存在.现在,前几天我试图重复那个小特技,但后来使用了 .NET 4.5 机器,我再也做不到了.
A couple of years ago I amused myself by setting Empty
to some other string instance through reflection, and see how many parts of the BCL started behaving strangely because of it. It was quite many. And the change of the Empty
reference seemed to persist for the complete life of the application. Now, the other day I tried to repeat that little stunt, but then using a .NET 4.5 machine, and I couldn't do it anymore.
(注意!如果你的机器上有 .NET 4.5,可能你的 PowerShell
仍然使用旧版本的 .NET(仅适用于 Windows 7 或更旧版本,其中 PowerShell 有PowerShell 2.0 之后没有更新),所以尝试将 [String].GetField("Empty").SetValue($null, "Hello world!")
复制粘贴到 PowerShell 中以查看更改此引用的一些影响.)
(NB! If you have .NET 4.5 on your machine, probably your PowerShell
still uses an older version of .NET ( only true for Windows 7 or older where PowerShell has not been updated beyond PowerShell 2.0), so try copy-pasting [String].GetField("Empty").SetValue($null, "Hello world!")
into PowerShell to see some effects of changing this reference.)
当我试图寻找原因时,我偶然发现了一个有趣的话题.NET 4.5 中出现这个 FatalExecutionEngineError 的原因是什么测试版?".在该问题的公认答案中,是否注意到通过 4.0 版,System.String
有一个静态构造函数 .cctor
,其中字段 Empty
> 已设置(在 C# 源代码中,当然,这可能只是一个字段初始值设定项),而在 4.5 中不存在静态构造函数.在两个版本中,字段本身看起来都一样:
When I tried to search for a reason for this, I stumbled upon the interesting thread "What's the cause of this FatalExecutionEngineError in .NET 4.5 beta?". In the accepted answer to that question, is it noted that through version 4.0, System.String
had a static constructor .cctor
in which the field Empty
was set (in the C# source, that would probably just be a field initializer, of course), while in 4.5 no static constructor exists. In both versions, the field itself looks the same:
.field public static initonly string Empty
(如 IL DASM 所见).
(as seen with IL DASM).
除了 String::Empty
之外,似乎没有其他字段受到影响.例如,我试验了 System.Diagnostics.Debugger::DefaultCategory
.这种情况看起来很相似:一个密封的类,包含一个 static readonly
(static initonly
) 类型为 string
的字段.但在这种情况下,通过反射改变值(引用)工作正常.
No other fields than String::Empty
seems to be affected. As an example, I experimented with System.Diagnostics.Debugger::DefaultCategory
. This case seems analogous: A sealed class containing a static readonly
(static initonly
) field of type string
. But in this case it works fine to change the value (reference) through reflection.
回到问题:
从技术上讲,当我设置字段时,Empty
似乎没有改变(在 4.5 中)怎么可能?我已经验证 C# 编译器不会欺骗"读取,它输出 IL 如下:
How is it possible, technically, that Empty
doesn't seem to change (in 4.5) when I set the field? I have verified that the C# compiler does not "cheat" with the read, it outputs IL like:
ldsfld string [mscorlib]System.String::Empty
所以应该读取实际字段.
so the actual field ought to be read.
在对我的问题进行悬赏后请注意写入操作(这肯定需要反射,因为该字段是 readonly
(又名 initonly
在 IL)) 实际上按预期工作.异常的是读取操作.如果你用反射读取,如在 typeof(string).GetField("Empty").GetValue(null)
中,一切正常(即看到值的变化).请参阅下面的评论.
Edit after bounty was put on my question: Note that the write operation (which needs reflection for sure, since the field is readonly
(a.k.a. initonly
in the IL)) actually works as expected. It is the read operation which is anomalous. If you read with reflection, as in typeof(string).GetField("Empty").GetValue(null)
, everything is normal (i.e. the change of value is seen). See comments below.
所以更好的问题是:为什么这个新版本的框架在读取这个特定字段时会作弊?
So the better question is: Why does this new version of the framework cheat when it reads this particular field?
推荐答案
不同之处在于新版本 .NET 的 JIT,它显然通过内联引用优化了对 String.Empty
的引用到特定的 String
实例,而不是加载存储在 Empty
字段中的值.这在 ECMA-335 Partition I §8.6.1.2 中 init-only 约束的定义下是合理的,可以解释为 String.Empty
的值String
类初始化后字段不会改变.
The difference lies in the JIT for the new release of .NET, which apparently optimizes references to String.Empty
by inlining a reference to a particular String
instance rather than load the value stored in the Empty
field. This is justified under the definition of the init-only constraint in ECMA-335 Partition I §8.6.1.2, which can be interpreted to mean the value of the String.Empty
field will not change after the String
class is initialized.
这篇关于更改了 .NET 4.5 中 string.Empty(或 System.String::Empty)的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!