我相信 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
当我根据上面的列表考虑它应该如何发生时,我认为它应该是这样的:
- 点静态字段(在我的情况下没有?
- 点静态构造函数 -> 将某些内容设置为"Bar">
- 点实例字段
- 基本静态字段 -> 将某些内容设置回"Hello"? ...
但是,它并没有将某些东西设置回 Hello 这真的让我感到困惑。那么我该如何解释呢?还是对象初始化与我所说的不同?
在基UiBase
类构造函数中调用虚拟成员ToString()
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
类中没有副本,它的值将在任何地方更改。根据静态成员
静态成员只有一个副本,无论有多少个 将创建类的实例。
如果你在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
静态初始化之前初始化。