在.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为string
的static readonly
( static 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::Empty
和System.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有什么含义?