在ECMA规范术语中,像GetField和GetValue这样的反射方法执行的是“引用”还是“访问”?

7
我真正想知道的是哪些反射方法会触发类型初始化?这对我来说有点不清楚。具体来说,如果应用于静态字段,那么所提到的两种方法GetField和GetValue是否会触发类型初始化?我已经尝试研究了这个问题,据我所知,执行诸如引用或访问静态字段等操作将触发所有静态字段的类型初始化。下面我引用了我认为相关的规范部分,但使用"引用"和"访问"等措辞恰恰是我的犹豫所在:
到底哪些操作才算是"访问"?
获取字段的FieldInfo元数据是否算作"引用"或"访问"该字段?

请帮助我找到规范的相关部分,以便我知道我的代码*是安全的、符合规范的,而不仅仅是因为某些未记录的实现细节或行星恰好排列正确而"碰巧工作"。

我的代码通过了测试,但依赖于类型初始化行为。我在这里没有展示我的代码,因为它很冗长,问题也不是想要一个“你的代码看起来不错”的回复,而是我想学习如何以及为什么这样做,以便自己评估我的代码是否符合规范,并且(假设符合规范)思考我可以对其进行哪些更改而无需每次都问一个新问题。请注意保留HTML标签。

到目前为止,我了解以下规范的内容,其中使用了上述术语“引用”和“访问”:

我知道ECMA-334(C#语言规范),静态字段初始化,第17.4.5.1节

如果类中存在静态构造函数(§17.11),则在执行该静态构造函数之前立即执行静态字段初始化。否则,在该类的静态字段第一次使用之前,以实现相关的时间执行静态字段初始化。

还知道ECMA-334(C#语言规范),静态构造函数,第17.11节

一个非泛型类的静态构造函数在给定应用程序域中最多执行一次。对于从类声明构造的每个封闭构造类型,泛型类声明的静态构造函数最多执行一次(§25.1.5)。触发静态构造函数的执行是由以下事件中发生的第一个事件触发的:
- 创建该类的实例。 - 引用了该类的任何静态成员。
如果一个类包含Main方法(§10.1),其中执行开始,则该类的静态构造函数在调用Main方法之前执行。如果一个类包含任何具有初始值设定项的静态字段,则这些初始化器将在执行静态构造函数之前按文本顺序立即执行(§17.4.5)。
更相关的是ECMA-335(CLI规范),类类型定义,第I部分,第8.9.5节。

[...] 这种类型初始化方法的执行时机和触发条件如下:

  1. 一个类型可以有一个类型初始化方法,也可以没有。
  2. 一个类型可以被指定为其类型初始化方法具有松散的语义(为了方便起见,我们称之为BeforeFieldInit)。
  3. 如果标记为BeforeFieldInit,则该类型的初始化程序方法将在第一次访问该类型定义的任何静态字段之前或之后执行。
  4. 如果未标记为BeforeFieldInit,则该类型的初始化程序方法将在以下情况下触发执行:
    a. 访问该类型的任何静态字段时首次访问
    b. 调用该类型的任何静态方法时首次调用
    c. 如果它是值类型,则调用该类型的任何实例或虚拟方法时首次调用
    d. 调用该类型的任何构造函数时首次调用。
  5. 任何类型的初始化程序方法的执行都不会触发自动执行其基类型定义的任何初始化程序方法,也不会触发其实现的任何接口的初始化程序方法。

相关的MSDN链接:
Type.GetField方法
FieldInfo类
FieldInfo.GetValue方法


2
这怎么会成为“基于观点”的关闭投票理由呢?有时候规格说明可能会令人困惑,我只是想知道我是否正确地解释了它们。如果我很幸运,其中一个设计师会回答它,因此就不会涉及任何基于观点的问题了。 - AnorZaken
1
是的,那是一个奇怪的关闭投票。反射超出了C#规范的范畴,因为它是CLI的东西,而不是C#的东西。反射方法调用基本上指示运行时发出和/或执行相关的IL,这将调用适当主题的C#规范的相关规则,例如类型初始化,在这里C#和CLI之间的重叠非常严重(基本上C#规范在那里承诺CLI事物,而不是特定于C#的事物)。但这只是我脑海中的一个解释,我找不到一个明确的来源来更好地解释它。 - CodeCaster
2
我觉得仅仅检查类型信息(如GetField())并不算是"访问"或"引用"实例或类型。但是,一旦调用FieldInfo.GetValue(),就会算作访问。但这只是我的直觉,缺乏证实,因此没有发布为答案。 - CodeCaster
1
仅检查Type对象本身不会引发此构造函数,检查字段、属性、方法或类型本身的属性也不会引发。 - Lasse V. Karlsen
1
@hazzik 我重新阅读了你的离题链接,对我而言似乎非常相关。也许自从你上次阅读它以来已经进行了修订?“如果您的问题通常涵盖软件开发中独特的实际可答复问题,则您在这里提问是正确的!” 我认为该声明后面没有任何附加条件。 (但感谢您抽出时间进行评论,这非常有帮助,如果我错了,我想要理解原因。) - AnorZaken
显示剩余9条评论
1个回答

1
我真正想知道的是哪些反射方法会触发类型初始化?具体来说,如果应用于静态字段,是否会触发类型初始化,包括GetField和GetValue这两种方法?
FieldInfo.GetValue会触发类型初始化。这是从实验观察中得出的结论。这完全取决于实现,没有证据支持。它不一定适用于所有情况,因为Reflection不需要遵循任何规格,规范也不涵盖Reflection。有一些迹象可以获得未初始化的字段,但我无法编写代码使其发生。
typeof()、Type.GetType和Type.GetField很可能不会触发类型初始化。但同样,这是从观察中得出的结论。
如果您需要确保类型初始化程序在/之前的某个特定时间被调用,您需要调用RuntimeHelpers.RunClassConstructor方法。这是唯一保证类型初始化程序在应用程序域的生命周期中被调用一次的方法。
“访问”实际上指哪些操作?
这些操作都不算,“反射”没有被规范所涵盖,因此这些术语在这里不适用。
获取字段信息元数据是否算作“引用”或“访问”该字段?
都不算。
从规范上来看并不清楚,但我理解“访问”和“引用”的区别如下:
- 当您调用此成员(方法或属性)或获取/设置其值(字段)时,就会发生访问。 - 当方法体中存在对该成员的访问表达式时,该方法就会“引用”该成员。
PS:仍然不清楚您在问什么以及您要解决什么具体问题。

我最终使用了RuntimeHelpers.RunClassConstructor。我的问题是确保在调用静态字段的GetValue时,不会获得未初始化的值。我确实重新设计了我的代码,以便在大多数情况下,我可以保证在反射它们之前已经初始化了这些字段,但仍有一个情况无法解决。在这种情况下,我使用了RunClassConstructor。事实上,我希望知道该值是否未初始化,但更聪明的做法是询问它是否触发了初始化,因为即使普通访问也可能返回未初始化的值。 - AnorZaken
感觉很奇怪,反射在CLI规范中明确指出紧凑档案(甚至不需要浮点支持)需要“反射库”的存在,但是却将其放在规范之外。你怎么能要求某物的存在而不指定它应该如何工作呢?开玩笑地说,这就像是说“你必须有一个Finkelding-我不知道它是什么,但它必须在那里!如果你没有Finkelding,你就不符合标准!”这肯定让我不高兴。:( - AnorZaken
@AnorZaken 我认为你需要将这个第一条评论和未能成功的案例(以及成功的案例)放到问题中,以使其更清晰。 - hazzik
我只是想知道类型初始化是否被触发,没有其他需要帮助的地方。虽然有些复杂,但基本上一个通用类在我的库之外的一个类中定义了一堆静态字段,而从一个以该类型作为通用类型参数的方法中,我需要确保该类型的所有静态字段都已初始化。由于我已经知道访问一个静态字段将触发同一类上所有静态字段的初始化,所以我只需要反射的保证或另一种触发它的方式,即RunClassConstructor。 - AnorZaken
要明确的是,问题不在于我有一个无法工作的案例,而是我有一个不能保证能够工作的案例。谁想要发布不能保证能够工作的代码呢?这样做让人感到不安。 - AnorZaken
尽管这不是我所希望的答案,但也许这是我能得到的最好的答案 - 而且我确实承诺了赏金,所以就请你拿去吧。 - AnorZaken

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接