为什么CLR总是呼叫值类型构造函数
-
15-09-2020 - |
题
我有一个关于值类型中的类型构造函数的问题。这个问题是由杰弗里里希特在CLR中写入的东西的启发,他说(第195页 - 第8章)你永远不应该在价值类型中定义一个类型的构造函数,因为CLR不会呼叫时代它。
所以,例如(嗯...... Jeffrey Richters榜样实际上),即使通过查看IL,我也无法解决,为什么在以下代码中没有调用类型构造函数:
internal struct SomeValType
{
static SomeValType()
{
Console.WriteLine("This never gets displayed");
}
public Int32 _x;
}
public sealed class Program
{
static void Main(string[] args)
{
SomeValType[] a = new SomeValType[10];
a[0]._x = 123;
Console.WriteLine(a[0]._x); //Displays 123
}
}
.
所以,应用以下型构造函数的规则我只是看不到为什么没有调用上面的值类型构造函数。
- 我可以定义一个静态值类型构造函数来设置类型的初始状态。
- 类型可以没有多于一个构造函数 - 没有默认值。
- 类型构造函数隐式私有
- JIT编译器检查类型的类型构造函数是否已在此AppDomain中执行。如果不是它将呼叫发出到原生代码,否则它没有知道类型已经“初始化”。
- CLR构造类型阵列的方式。我想想在创建第一个项目 时调用静态构造函数
- 构造函数中的代码未初始化任何静态字段,以便忽略它。我已经尝试使用构造函数中的私有静态字段,但该字段仍然是默认值0值 - 因此构造函数未调用。
- 或......编译器是以某种方式优化由于设置的公共INT32而优化了构造函数调用 - 但这是一个模糊猜测!!
所以......我无法弄清楚为什么我看不到正在构造的这个类型的数组。
我最好的猜测是它可能是:
最佳实践等anside,我只是超级兴趣,因为我希望能够为自己看,为什么它没有被称为。
编辑:我在下面添加了一个答案,只是Jeffrey Richter对此的报价。
如果有人有任何想法,那么这将是辉煌的。 非常感谢, 詹姆斯
解决方案
Microsoft C#4规范已经从以前的版本略有变化,现在更准确地反映我们在此处看到的行为:
11.3.10静态构造函数
结构的静态构造函数跟随 大多数与课程相同的规则。 执行静态构造函数 对于结构类型被触发 首先发生以下事件 在应用程序域中:
- 参考结构类型的静态成员。
- 调用结构类型的明确声明的构造函数。
创建默认值 (§ 11.3.4)结构类型没有 触发静态构造函数。 (一个 这是初始值 阵列中的元素。)
ecma spec 和 Microsoft C#3规格在该列表中有一个额外的事件:“参考结构类型的实例成员”。 <罢工>所以它看起来好像C#3在这里违反自己的规格。 C#4规范已经与C#3和4的实际行为更接近对齐。
在进一步调查之后,看起来几乎是所有实例成员访问除直接现场访问外,触发静态构造函数(至少在当前的C#3和4的Microsoft实现中)。
因此,当前实现与eCMA和C#3规格中的规则更密切相关,而不是C#4规范中的规则:在访问所有实例成员之外,正确地实现了C#3规则除< / em>字段; C#4规则是仅在实地访问中正确实现。其他提示
从标准§18.3.10(另请参见 C#编程语言书):
在应用程序域中的第一个事件中,首先触发用于结构的静态构造函数:
- 结构的实例成员 引用。
- 一个静态成员 参考结构。
- 一个明确声明的构造函数 调用struct。
[注意:创建 默认值(&#167; 18.3.4)的结构 类型不会触发静态 构造函数。 (这是一个例子 元素的初始值 数组。)结束注意]
所以我会同意你的程序,你的程序的最后两行应每个都应该触发第一个规则。
测试后,共识似乎是它始终如一地触发方法,属性,事件和索引器。这意味着除字段之外,所有显式实例成员都是正确的。因此,如果为标准选择了Microsoft的C#4规则,那将使他们的实现从大多数情况下从主要是错误的。
只是把它作为一个“答案”,所以我可以分享里希特先生自己写了什么(任何有人对最新的CLR规范有一个链接,它很容易获得2006年版,但找到它更难获得最新的一个):
对于这种东西,通常更好地查看CLR Spec而不是C#规范。 CLR规范说:
4。如果未标记为BEFIELDININIT,那么该类型的初始化程序方法在(即,被触发)执行:
•首先访问该类型或
的任何静态字段•首次调用该类型或
的任何静态方法•首先调用该类型的任何实例或虚拟方法,如果它是值类型或
•首先调用该类型的任何构造函数。
由于没有满足这些条件,因此静态构造函数是不调用。注意的唯一棘手的部分是“_x”是一个实例字段不是静态字段,并且构造一个结构数组是 not 在数组元素上调用任何实例构造函数。
另一个有趣的样本:
struct S
{
public int x;
static S()
{
Console.WriteLine("static S()");
}
public void f() { }
}
static void Main() { new S().f(); }
. 更新:我的观察是除非使用静态状态,否则静态构造函数将永远不会被触摸 - 运行时似乎决定的东西并不适用于引用类型。如果它是一个剩下的错误,这就提出了问题,因为它没有影响,它是通过设计,或者是一个待处理的错误。
更新2:个人,除非您在构造函数中做的东西时,否则运行时的这种行为永远不会导致问题。一旦您访问静态状态,它就会正确行事。
update3:继Lukeh进一步评论,并引用Matthew Flaschen的答案,在结构中实现和调用您自己的构造函数也触发了要调用的静态构造函数。意思是,在三个场景中的一个中,行为不是它在锡上所说的。
我刚刚为类型添加了一个静态属性并访问了该静态属性 - 它称为静态构造函数。如果没有静态属性的访问,只需创建类型的新实例,静态构造函数未调用。
internal struct SomeValType
{
public static int foo = 0;
public int bar;
static SomeValType()
{
Console.WriteLine("This never gets displayed");
}
}
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
// Doesn't hit static constructor
SomeValType v = new SomeValType();
v.bar = 1;
// Hits static constructor
SomeValType.foo = 3;
}
}
.
此链接中的一个注释指定在简单访问实例时,静态构造函数 not :
我会猜测您正在创建值类型的数组。因此,新关键字将用于初始化数组的内存。
它有效地说
SomeValType i;
i._x = 5;
.
没有新的关键字随时随地,这基本上是你在这里在做什么。是SomevalType参考类型,您必须使用
初始化数组的每个元素array[i] = new SomeRefType();
. 这是MSIL中“preasuffieldInit”属性的疯狂设计行为。它也影响了C ++ / CLI,我提交了一个错误报告,其中Microsoft非常解释了为什么行为是它的方式,我指出了不同意/需要更新的语言标准中的多个部分来描述实际行为。但它不公开。无论如何,这里是微软的最后一句话(讨论了C ++ / CLI中的类似情况):
自从我们调用标准 这里,来自分区I的行,8.9.5 说:
如果在FRIELDININIT之前标记为那么 执行类型的初始化程序方法 在之前或之前,首先访问 对其定义的任何静态字段 类型。
该部分实际上进一步详细 关于语言如何实现 可以选择防止行为 你正在描述。 C ++ / CLI选择没有 到,而是允许程序员 如果他们希望这样做。
基本上,由于下面的代码有 绝对没有静态字段,JIT 根本不完全正确 调用静态类构造函数。
相同的行为是您所看到的,虽然以不同的语言。