.NET类加载器是否有标准的工作方式?

3

.NET类加载器有标准的工作方式吗?

假设我编译了以下代码:

Option Strict On : Option Explicit On

Module Module1
    Sub Main()
        System.Diagnostics.Debug.WriteLine("Main")
        Dim g = C.A
    End Sub
End Module

Public Class C
    Shared Sub New()
        System.Diagnostics.Debug.WriteLine("Init C")
    End Sub
    Shared Property A As New A
End Class

Public Class A
    Shared Sub New()
        System.Diagnostics.Debug.WriteLine("Init A")
    End Sub
    Public Sub New()
        System.Diagnostics.Debug.WriteLine("A Constructor")
    End Sub
End Class

我能保证编译后的代码在所有实现平台上都会产生以下输出吗?

Main
Init A
A Constructor
Init C

2
“类加载器”意味着其他的东西。你所说的是“类初始化器”,也称为“静态构造函数”。 - Hans Passant
Jon Skeet在这里讨论了类型初始化(带有一些.NET 4的更改):http://msmvps.com/blogs/jon_skeet/archive/2010/01/26/type-initialization-changes-in-net-4-0.aspx - Govert
@Hans:也许是这样,但我们也可以合理地想象运行时类加载器是否保证在特定时间调用类型初始化程序。(实际上,仅对于标记为beforefieldinit的类型,类加载器才有权这样做,否则必须按照规范指示稍后调用类型初始化程序 - Ben Voigt
2个回答

5

是的,调用静态构造函数和实例构造函数的顺序是语言规范的一部分。所有符合规范的编译器应该为这个程序生成相同的IL。


1
同时,“相同的IL”为运行时留下了一些余地,特别是在使用beforefieldinit时,可以灵活控制类型初始化器的运行时间。 - Ben Voigt

4
由于您正在使用构造函数而不是内联初始化,因此VB编译器不会标记类型为beforefieldinit,并且顺序完全受控制。
但是,如果您编写了以下内容(使用内联初始化):
Option Strict On : Option Explicit On

Module Module1
    Sub Main()
        System.Console.WriteLine("Main")
        Dim g = C.A
    End Sub
End Module
Public Class C
    Shared Function Narg() As A
        Dim alpha As New A
        System.Console.WriteLine("Init C")
        Return alpha
    End Function
    Shared Property A As A = Narg()
End Class
Public Class A
    Shared Sub New()
        System.Console.WriteLine("Init A")
    End Sub
    Public Sub New()
        System.Console.WriteLine("A Constructor")
    End Sub
End Class

顺序是未指定的。允许与您的原始代码相同的顺序,但可以更早地初始化C。实际上,在我的系统上,输出为:

Init A
A Constructor
Init C
Main

原因是C现在被标记为beforefieldinit


无端挑剔:不,您的原始代码不能保证具有该输出。在发布版本中,它将没有输出,因为System.Diagnostics.Debug是有条件调用的。


我不明白这个输出是如何合法的:在进行“Init C”输出之前,通过“New”创建了“A”的一个实例。优化器是否重新排列了指令?为什么它可以在这里被允许呢?这与初始化顺序无关,我们只是有两个具有副作用的相邻语句。 - Konrad Rudolph
@Konrad:beforefieldinit 允许 C 初始化程序在使用 C.A 之前的任何时间运行。因此,在这种情况下,它实际上是在 Main 之前运行的。Init A - A Constructor - Init C 的顺序没有改变(也不能改变)。在 Narg() 中构造了一个新的 A,它调用了 A Constructor。但是,A Constructor 无法运行,直到 A 的初始化程序运行,因此 JIT 首先运行它。在 A Constructor 返回后,Narg() 继续运行并打印 Init C。这就引起了所提到的子序列。 - Ben Voigt
到目前为止还不错。但是 A 的构造函数 需要在 Main 之前运行,因为它在初始化器中被调用,在控制台输出 "Init C" 之前。 - Konrad Rudolph
@Konrad:A 的构造函数确实在 Main 之前运行,尽管这并不是保证的。它只保证在打印出 Init C 这一行之前运行,而 beforefieldinit 允许它在 Main 打印之后或任何更早的时间运行。 - Ben Voigt
啊,我明白了。我只是在某种程度上错误地读错了你的输出,并认为 Init C 是在 A Constructor 之前。我的错误。 - Konrad Rudolph

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