【问题标题】:My C# program does not initialize the Object how I think it has to according to the Object initialization order. Why?我的 C# 程序没有按照我认为的对象初始化顺序初始化对象。为什么?
【发布时间】:2020-05-02 05:43:47
【问题描述】:

我相信 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. Point 静态构造函数 -> 将 Something 设置为“Bar”
  3. 点实例字段
  4. 基本静态字段 -> 将Something 设置回“Hello”? ...

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

【问题讨论】:

  • 你在这一行输出了什么? Console.WriteLine( 它不会编译
  • 抱歉复制意大利面错误。我纠正了它
  • 不,在创建任何对象实例之前,静态构造函数都必须先出现。
  • 静态的、基础的、派生的。在未密封的类的构造函数中调用虚方法是有风险的。 C++ 语言值得注意,它防止方法在构造函数完成运行之前表现为虚拟。为了防止出现这样的意外,尽管程序员现在在您想要惊喜时往往会感到难过。 C# 不这样做。

标签: c# .net initialization


【解决方案1】:

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

Console.WriteLine(this.ToString());

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

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

this 尚未完全初始化,您将在输出中获得-1。由于ToString() 是虚方法,因此调用Point.ToString(),根据specs

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

在创建Point 的实例或引用任何静态成员之前自动调用静态构造函数(详情请查看static constructors

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

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

Something 字段完全针对UiBase,在Point 类中没有副本,它的值会随处改变。根据static members

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

如果您在Console.WriteLine(x); 之后打印UiBase.Something,您将得到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静态初始化之前被初始化。

【讨论】:

  • 是的,但是我的帖子中的订单呢?按照它清楚地说明派生静态构造函数在基本静态字段初始化之前调用的顺序。因此,基于此,我希望它运行点的静态 ctor,将 Something 更改为 Bar,然后使用值“Hello”初始化基本字段 Something。那么为什么它没有像命令所说的那样发生呢?这是否意味着顺序不正确?
  • 如果静态字段变量初始化器存在于静态构造函数的类中,它们将按照它们在执行静态之前出现在类声明中的文本顺序执行 protected static string Something = "Hello"; 在静态构造函数之前执行。 UiBase静态初始化在Point静态初始化之前调用,因为Point继承UiBase
  • 所以顺序错了?这意味着它是派生静态字段->基础静态字段->派生静态ctor->派生实例字段->基础静态ctor ...?
  • 静态初始化发生在任何实例初始化之前
  • @MasterR8 我在您的代码中添加了一个步骤,您可以查看实际顺序
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-13
  • 2010-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-02-10
相关资源
最近更新 更多