为啥我可以通过反射在字段初始化后重写静态只读字段?
Posted
技术标签:
【中文标题】为啥我可以通过反射在字段初始化后重写静态只读字段?【英文标题】:Why I can rewrite static readonly field after field initiaization by reflection?为什么我可以通过反射在字段初始化后重写静态只读字段? 【发布时间】:2020-08-14 20:33:12 【问题描述】:我们知道静态构造函数是在第一次调用类时调用的。
我们现在有一个类:
static class Demo
private readonly static int _intValue;
static Demo ()
_intValue = 5;
在访问Demo
时将调用构造函数。值 5 将分配给 _intValue
只读字段。之后,我们可以再次通过反射设置值:
typeof (Demo)
.GetField ("_ intValue", BindingFlags.Static | BindingFlags.NonPublic)
.SetValue (null, 567);
为什么值5
可以被覆盖?它不是已经在构造函数中初始化了吗?为什么不抛出System.FieldAccessException
?
在 NET Core 3.1 中测试。完整示例https://dotnetfiddle.net/4DsJ6H
更新: 我找到了一种方法来覆盖只读静态字段,即使它被请求一次。它基于通过 IL 生成的方法。实际上,除了最初的问题 - 这种解决方法是如何工作的?
https://dotnetfiddle.net/oYaf5t
【问题讨论】:
Reflection 让您可以做普通 C# 代码甚至编译后的 IL 代码通常做不到的事情。例如,您可以从其声明类型之外调用私有方法。这似乎是一个类似的案例。 @JoeSewell 为什么你没有提到它作为答案? @JoeSewell 我很感兴趣这个任务是如何详细完成的,它绕过了只读字段的限制?这是由环境提供的吗?为什么?或者这只是 .NET Core 的一个特性?例如,在 NET Framework 4.7 中,通过反射根本找不到静态只读字段。这有什么关系吗?我只是不知道要掌握什么才能更好地理解问题中描述的行为。 “在 NET Framework 4.7 中,根本不会通过反射找到静态只读字段”——这根本不是真的;而在 .net core 3.1 中,您 不能 依赖于能够通过反射获得静态 ReadOnly 字段,因为它会影响某些新的 JIT 优化(但这可能仅适用于 ref 类型(类),对于去虚拟化) 这里是@MarcGravell 的更深层次的discussion。 【参考方案1】:反射已经打破了所有规则,包括可访问性和可变性;它实际上和unsafe
一样强大:就像unsafe
一样:如果出现问题,它是自己造成的,运行时会嘲笑你。
请注意,在 .NET Core 中,运行时有时会阻止您执行此操作,因为如果您这样做,JIT 优化将变得无效。但如果它不在这里:很好。
注意:您过去可以通过反射更改string.Empty
。想象一下结局有多好:)
【讨论】:
"注意:你曾经能够更改 string.Empty" 我在用我的调度程序替换 TaskScheduler.Default 时问了上面的问题。当然,不是在产品中:) 我是否正确理解在这个简单的示例中,运行时允许覆盖只读静态字段,因为它正确地执行了 JIT?如果是这样,你能举例说明什么时候不能这样做吗? @KregHEk Sean Skelly 已经为您链接了一个。也许与您的示例是值类型有关,因此不需要(或可以)进行去虚拟化。 @KregHEk 忍不住;这是另一个带有示例的链接,这次是a SO answer。 tl; dr:与仅具有静态只读字段相比,同时具有静态构造函数 和 静态只读字段的组合会在设置字段时导致 IL 中的细微差异。但一定要通读它并注意“beforefieldinit”。以上是关于为啥我可以通过反射在字段初始化后重写静态只读字段?的主要内容,如果未能解决你的问题,请参考以下文章