C# 构造函数执行顺序

160

在C#中,当您执行

Class(Type param1, Type param2) : base(param1) 

类的构造函数首先被执行,然后调用超类的构造函数,还是先调用基类的构造函数?


1
我从未使用过XNA,但是我没有看到你的“问题”中有一个问题。 - Andrew Song
4
就这些吗?这是你能提供的全部线索吗?来吧,给我们一个提示……动物、蔬菜、矿物质? - Pete Duncanson
2
我按下按钮太快了 xD... 抱歉,我还在写着... - webdreamer
1
C#语言规范 - zwcloud
7个回答

207

顺序如下:

  • 对于层次结构中的所有类,成员变量会被初始化为默认值

然后从最派生的类开始:

  • 对于最派生类型,变量初始化器将被执行
  • 构造函数的链式调用会决定将要调用哪个基类构造函数
  • 递归地初始化基类(全部执行)
  • 在此类中执行构造函数体(请注意,如果它们使用 Foo() : this(...) 等进行链接,则可能有多个构造函数)

请注意,在 Java 中,基类在运行变量初始化器之前初始化。如果您要移植任何代码,则需要了解这个重要的区别 :)。

如果您感兴趣,可以查看更多详细信息的页面


6
这里好像有一半的回答都是指向他的答案;但Eric Lippert也在这个主题上写了一篇很好的文章: http://blogs.msdn.com/ericlippert/archive/2008/02/15/why-do-initializers-run-in-the-opposite-order-as-constructors-part-one.aspx http://blogs.msdn.com/ericlippert/archive/2008/02/18/why-do-initializers-run-in-the-opposite-order-as-constructors-part-two.aspx - Matt Enright
1
在VB.NET中,派生类字段初始化程序在基类初始化完成后运行(这是我个人认为的正确方式)。除此之外,这意味着可以使用正在构建的对象的字段或属性来初始化派生类字段。 - supercat
1
@supercat: 另一方面,这意味着您无法调用基类中的任何成员,因为该部分尚未初始化... - Jon Skeet
2
@Jon Skeet:在 VB.NET 中,您可以从派生类初始化器中调用基类成员。从基类构造函数中虚拟调用的派生类方法不能依赖于它们的字段初始化程序已运行,但是我认为依赖于派生类字段初始化程序在基础构造函数之前运行的设计总体上来说是比较脆弱的,因为它们无法适应任何初始化值取决于构造参数的情况。 - supercat
2
@Jon Skeet:不,我的意思是所有与基类有关的内容都在派生类之前执行。实际上,我最希望的是声明允许四种字段模式:(1)在基础构造函数之前初始化;(2)在基础构造函数之后初始化,并允许使用基础成员进行计算;(3)从具有相同名称/格式的构造函数参数初始化(每个构造函数必须具有这些名称和类型的参数或链接到具有这些参数的构造函数),或(4)作为“临时”字段,初始化为#3,但仅可从其他初始化程序访问。 - supercat
显示剩余4条评论

68

首先会调用基础构造函数。此外请注意,如果您在构造函数后没有加上:base(param1),则将调用基类的默认构造函数。


21

我不确定这是否应该成为一个评论/答案,但对于那些通过示例学习的人来说,这个小代码演示也很好地说明了顺序:https://dotnetfiddle.net/kETPKP

using System;

// order is approximately
/*
   1) most derived initializers first.
   2) most base constructors first (or top-level in constructor-stack first.)
*/
public class Program
{
    public static void Main()
    {
        var d = new D();
    }
}

public class A
{
    public readonly C ac = new C("A");

    public A()
    {
        Console.WriteLine("A");
    }
    public A(string x) : this()
    {
        Console.WriteLine("A got " + x);
    }
}

public class B : A
{
    public readonly C bc = new C("B");

    public B(): base()
    {
        Console.WriteLine("B");
    }
    public B(string x): base(x)
    {
        Console.WriteLine("B got " + x);
    }
}

public class D : B
{
    public readonly C dc = new C("D");

    public D(): this("ha")
    {
        Console.WriteLine("D");
    }
    public D(string x) : base(x)
    {
        Console.WriteLine("D got " + x);
    }
}

public class C
{
    public C(string caller)
    {
        Console.WriteLine(caller + "'s C.");
    }
}

结果:

D's C.
B's C.
A's C.
A
A got ha
B got ha
D got ha
D

21

首先调用基类的构造函数。


2

[编辑:在我回答的时间内,问题已经完全改变了]

答案是它首先调用基类。

[下面是旧问题的原始答案]

你是否在问何时执行构造函数调用中的“base”部分?

如果是这样,如果该类派生自另一个具有此构造函数的类,则会“链接”到构造函数基类:

  public class CollisionBase
    {
        public CollisionBase(Body body, GameObject entity)
        {

        }
    }

    public class TerrainCollision : CollisionBase
    {
        public TerrainCollision(Body body, GameObject entity)
            : base(body, entity)
        {

        }
    }

在这个例子中,TerrainCollision 继承自 CollisionBase。通过这种方式链接构造函数,确保指定的构造函数使用提供的参数在基类上被调用,而不是默认构造函数(如果基类有默认构造函数)。

1

您的问题有点不清楚,但我假设您的意思是问以下问题:

在我的XNA对象中何时调用基本构造函数vs.使用隐式默认构造函数

这个答案高度取决于您的场景和底层对象。您能否通过以下方式澄清一下:

  • 什么是情景?
  • TerrainCollision的基本对象类型是什么?

我最好的答案是,如果您有与基类构造函数的参数相对应的参数,则几乎肯定应该调用它。


0

构造函数机制更好,因为它允许应用程序使用构造函数链接,如果您要扩展应用程序,则通过继承使得能够进行最小代码更改。

Jon Skeets文章

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