调用静态构造函数和实例构造函数

据我所知,父类的构造函数先调用然后调用子类。但是为什么在静态构造函数的情况下它首先从派生类执行,然后是子类?

namespace ConsoleApplication1 { class Program { static void Main(string[] args) { Child t = new Child(); } } class Parent { public Parent() { Console.WriteLine("Parent Instance Constructor"); Console.ReadKey(); } static Parent() { Console.WriteLine("Parent Static Constructor"); Console.ReadKey(); } } class Child : Parent { public Child() { Console.WriteLine("Child Instance Constructor"); Console.ReadKey(); } static Child() { Console.WriteLine("Child Static Constructor"); Console.ReadKey(); } } } 

输出:

子静态构造函数

父静态构造函数

父实例构造函数

子实例构造函数

现在按照Jeppe Stig Nielsen建议,当我在构造函数中初始化静态字段时,它按以下顺序运行

产量

父静态构造函数

子静态构造函数

父实例构造函数

子实例构造函数

 class XyzParent { protected static int FieldOne; protected int FieldTwo; static XyzParent() { // ! FieldOne = 1; Console.WriteLine("parent static"); } internal XyzParent() { // ! FieldOne = 10; // ! FieldTwo = 20; Console.WriteLine("parent instance"); } } class XyzChild : XyzParent { static XyzChild() { // ! FieldOne = 100; Console.WriteLine("child static"); } internal XyzChild() { // ! FieldOne = 1000; // ! FieldTwo = 2000; Console.WriteLine("child instance"); } } 

为何如此矛盾的行为呢?

首先,这种行为完全不矛盾; 这一切都符合规则。 你只是不知道规则是什么。

您应该阅读我关于实例构造函数的两部分系列文章以及关于静态构造函数语义的四部分系列文章。 他们从这里开始:

http://blogs.msdn.com/b/ericlippert/archive/2008/02/15/why-do-initializers-run-in-the-opposite-order-as-constructors-part-one.aspx

和这里:

http://ericlippert.com/2013/02/06/static-constructors-part-one/

分别。

那些应该清楚地回答你的问题,但如果不是100%明确,请让我总结一下。 相关规则是:

  • 规则一 :静态构造函数在访问任何静态字段之前,执行任何静态方法之前以及执行任何实例构造函数之前运行。
  • 规则二 :派生类实例构造函数在运行派生类实例构造函数体之前调用基类实例构造函数。

那么当你执行new Child()时会发生什么?

  • 第一条适用。 我们将调用Child的实例构造函数,因此我们必须首先调用Child的静态构造函数。 所以它首先运行。
  • 在Child的静态构造函数返回之后,Child的实例构造函数运行。 规则二适用:Child实例构造函数在运行其主体之前执行的第一件事是运行Parent的实例构造函数。
  • 规则一再适用。 我们将调用Parent的实例构造函数,因此我们必须首先调用Parent的静态构造函数。 所以它运行。
  • 在Parent的静态构造函数返回之后,Parent的实例构造函数运行。 规则二适用:它调用object的实例构造函数,它没有任何兴趣,然后运行Parent的实例构造函数的主体。
  • Control返回Child的实例构造函数,并且其正文运行。

所以你去; 顺序是Child静态构造函数,然后是Parent静态构造函数,然后是Parent主体,然后是Child主体。

现在让我们看看你的第二个例子。 当你说new XyzChild时会发生什么?

  • 第一条适用。 我们将调用XyzChild的实例构造函数,因此我们首先调用XyzChild的静态构造函数。 它的身体开始执行,并且……
  • ……规则一再适用。 我们将要访问XyzParent的静态字段,因此必须执行XyzParent的静态构造函数。
  • XyzParent的静态构造函数执行。 它访问一个字段,但静态构造函数已经在这个线程上运行,因此它不会再次递归地触发静态构造函数。 它打印出它在父母中。
  • 控制返回到子的静态构造函数,该构造函数打印出它在子代中。
  • 现在孩子的实例构造函数可以运行了。 规则二适用:XyzParent的实例构造函数首先运行。
  • 规则一适用,但XyzParent的静态构造函数已经运行,因此它被跳过。
  • XyzParent的实例构造函数的主体执行并将控制返回给XyzChild的静态构造函数。
  • XyzChild的实例构造函数的主体运行。

你去吧 没有任何不一致之处; 这两个规则正确应用。

Static构造函数始终在非静态构造函数之前执行。 第一次访问类时会调用静态构造函数。

  • 静态构造函数

来自MSDN Doc ,

  • 静态构造函数不接受访问修饰符或具有参数。
  • 在创建第一个实例或引用任何静态成员之前,会自动调用静态构造函数来初始化类。
  • 无法直接调用静态构造函数。 用户无法控制何时在程序中执行静态构造函数。
  • 静态构造函数的典型用法是当类使用日志文件并且构造函数用于将条目写入此文件时。
  • 当构造函数可以调用LoadLibrary方法时,静态构造函数在为非托管代码创建包装类时也很有用。
  • 如果静态构造函数抛出exception,则运行时将不再调用它,并且该类型将在运行程序的应用程序域的生命周期内保持未初始化状态。

在您的情况下,静态构造函数的运行顺序是未定义的(我认为)。 唯一可以保证的是,它们将在创建实例之前运行。

我把你的代码改成了:

  class XyzParent { protected static int FieldOne; protected int FieldTwo; static XyzParent() { FieldOne = 1; Console.WriteLine("parent static"); } internal XyzParent() { FieldOne = 10; FieldTwo = 20; Console.WriteLine("parent instance"); } } class XyzChild : XyzParent { static XyzChild() { FieldOne = 100; Console.WriteLine("child static"); } internal XyzChild() { FieldOne = 1000; FieldTwo = 2000; Console.WriteLine("child instance"); } } 

现在重要的是它们运行的​​顺序,因为它们写入相同的字段。 用我的代码版本,说new XyzChild(); 导致这个输出:

 parent static child static parent instance child instance 

编辑:Eric Lippert的回答给出了更准确的解释。 上面的代码只在静态构造函数的末尾执行WriteLine 。 在静态构造函数的开头添加额外的WriteLine ,以查看XyzParent静态构造函数是否在XyzParent静态构造函数执行的“中间”运行。