在.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一个字符串?”。
几年前,我通过反思将Empty
设置为其他字符串实例,并且看到BCL中有多少部分开始因为它而奇怪地行为。 这是相当多的。 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
。 这种情况似乎是类似的:一个密封的类,包含一个类型为string
的static readonly
( static initonly
)字段。 但是在这种情况下,通过反射改变值(参考)可以很好地工作。
回到问题:
在技术上,如何在设置领域时Empty
似乎没有改变(4.5)? 我已经验证了C#编译器不会“读”作弊,它会输出如下所示的IL:
ldsfld string [mscorlib]System.String::Empty
所以应该阅读实际领域。
在赏金之后编辑我的问题:请注意写操作(需要反射肯定,因为该字段是readonly
(又名initonly
在IL))实际上按预期工作。 这是异常的读取操作。 如果使用反射读取,如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属性?
我只能假设这个属性被运行时捕获到做一些缓存?
因为它可以。
这些系统定义的initonly
字段的值是.NET运行时的全局不变量。 如果这些不变量被破坏,那么对于这种行为就不再有任何保证。
在C ++中,我们可能会有一条规则将此指定为导致未定义的行为。 在.NET中,它也是未定义的行为,只是没有任何规则说明System.String.Empty.Length > 0
时会发生什么。 .NET和C#的所有层的整个规范描述了当System.String.Empty.Length == 0
和一大堆不变量都成立时的行为。
有关在运行时间和含义之间有所不同的优化的更多信息,请参阅以下答案
上一篇: Changed behavior of string.Empty (or System.String::Empty) in .NET 4.5
下一篇: Is it better to call ToList() or ToArray() in LINQ queries?