0

我相信 C# 的对象初始化顺序是这样的:

  • 派生静态字段
  • 派生静态构造函数
  • 派生实例字段
  • 基本静态字段
  • 基础静态构造函数
  • 基本实例字段
  • 基实例构造函数
  • 派生实例构造函数

下面你会看到一个简单的测试程序以及我运行它时产生的输出。

    public class UiBase
    {
        protected static string Something = "Hello";

        public UiBase()
        {
            Console.WriteLine(this.ToString());
        }
    }

    public class Point : UiBase
    {
        private int X = -1;
        private int Y = -1;

        static Point()
        {
            Something = "Bar";
        }

        public Point(int x, int y)
        {
            X = x;
            Y = y;
        }

        public override string ToString()
        {
            return $"Point:{X}/{Y}/{Something}";
        }
    }

    public static class Program{
    public static void Main(){
        var x = new Point(2,1);
        Console.WriteLine(x);
    }
on Console:
Point:-1/-1/Bar
Point:2/1/Bar

当我根据上面的列表考虑它应该如何发生时,我相信它应该是这样的:

  1. 点静态字段(在我的情况下没有?)
  2. 点静态构造函数 -> 将Something设置为“Bar”
  3. 点实例字段
  4. 基本静态字段 -> 将某事设置回“你好”?...

然而,它并没有将Something设置回Hello,这真的让我感到困惑。那么我该如何解释呢?还是对象初始化与我所说的不同?

4

1 回答 1

6

ToString()您在基UiBase类构造函数中调用虚拟成员

Console.WriteLine(this.ToString());

Point它在构造函数之前被调用

public Point(int x, int y)
{
     X = x;
     Y = y;
}

this尚未完全初始化,您正在-1输出。由于ToString()是虚拟方法,因此按照规范Point.ToString()调用

调用最派生类中的重写成员,如果没有派生类重写该成员,则该成员可能是原始成员。

Point在创建实例或引用任何静态成员之前自动调用静态构造函数(请查看静态构造函数了解详细信息)

static Point()
{
     Something = "Bar";
}

它将Something从基类覆盖,并且Bar在这两种情况下都会得到输出。Something永远不会设置回Hello,它只会被覆盖一次。

Something字段是完全特定的UiBase,类中没有副本Point,它的值会随处改变。根据静态成员

无论创建了多少个类实例,都只存在一个静态成员的副本。

如果您UiBase.Something在 之后打印Console.WriteLine(x);,您将得到Bar,而不是Hello. 对于泛型类,只有一个例外,但这超出了您的问题范围。

就执行顺序而言,所有字段初始值设定项按从派生类到基类的顺序运行,然后所有构造函数按从基类到派生的顺序运行(这对于实例成员是正确的)。我为您的所有操作添加了一个步骤,以查看实际订单。

public class UiBase
{
    private static int temp = Step("uibase static field init");
    public static string Something = "Hello";

    private int _temp = Step("uibase instance field init");

    public static int Step(string message)
    {
        Console.WriteLine(message);
        return 0;
    }

    public UiBase()
    {
        Step("uibase instance ctor");
        Console.WriteLine(this.ToString());
    }
}

public class Point : UiBase
{
    private int _temp = Step("point instance field init");

    private int X = -1;
    private int Y = -1;

    static Point()
    {
        Step("point static ctor before");
        Something = "Bar";
        Step("point static ctor after");
    }

    public Point(int x, int y)
    {
        Step("point instance ctor");

        X = x;
        Y = y;
    }

    public override string ToString()
    {
        return $"Point:{X}/{Y}/{Something}";
    }
}

输出将如下

point static ctor before
uibase static field init
point static ctor after
point instance field init
uibase instance field init
uibase instance ctor
Point:-1/-1/Bar
point instance ctor
Point:2/1/Bar

首先Point调用静态构造函数(类中没有静态字段Point),然后它会“询问”UiBase初始化一个静态字段,因为访问它的Something值(它设置为Hello),然后Something设置为Bar并执行继续实例初始化(再次,Something永远不会再改变) - 派生类字段,基类字段,基类构造函数和派生类构造函数。

我认为,只有前 3 行可能会有点混乱,但是静态初始化只发生一次,并且在任何实例初始化之前发生。静态初始化的顺序由编译器根据您的实际代码确定。

添加UiBase静态构造函数实际上可以使画面更清晰,在这种情况下UiBase静态成员将在Point静态初始化之前进行初始化。

于 2020-01-11T14:13:03.943 回答