我的 C# 程序没有按照我认为的对象初始化顺序初始化对象。为什么?

Sid*_*eus 0 .net c# initialization

我相信 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
Run Code Online (Sandbox Code Playgroud)

当我根据上面的列表思考它应该如何发生时,我认为它应该是这样的:

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

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

Pav*_*ski 6

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

Console.WriteLine(this.ToString());
Run Code Online (Sandbox Code Playgroud)

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

public Point(int x, int y)
{
     X = x;
     Y = y;
}
Run Code Online (Sandbox Code Playgroud)

this尚未完全初始化,您正在进入-1输出。由于ToString()是虚方法Point.ToString(),根据规范被调用

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

Point创建的实例或引用任何静态成员之前自动调用静态构造函数(有关详细信息,请查看静态构造函数

static Point()
{
     Something = "Bar";
}
Run Code Online (Sandbox Code Playgroud)

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

Something字段完全特定于UiBasePoint类中没有副本,它的值将随处更改。根据静态成员

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

如果您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}";
    }
}
Run Code Online (Sandbox Code Playgroud)

输出将如下

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
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • _如果静态字段变量初始值设定项存在于静态构造函数的类中,则它们将按照它们在静态构造函数执行之前出现在类声明中的文本顺序执行。_ `protected static string Something = "Hello ";` 在静态构造函数之前执行。`UiBase`静态初始化在`Point`静态初始化之前调用,因为`Point`继承了`UiBase` (2认同)
  • 静态初始化发生在任何实例初始化之前 (2认同)