请考虑以下代码。字段i
和j
在m
和n
之前初始化。我们知道父对象是在子对象之前创建的,但是在我的程序中,编译器正在为基类之前的子类的成员变量分配和初始化内存。那是为什么?
class X
{
private int m = 0;
private int n = 90;
public X() { }
}
class Y:X
{
private int i = 8;
private int j = 6;
public Y()
{ }
public static void Main(string []args)
{
Y y1 = new Y();
}
}
答案 0 :(得分:5)
Eric Lippert's blog中解释了这一点:
[...]初始化的只读字段始终在其初始化状态中被观察到,除非我们首先运行所有初始值设定项,然后运行所有构造函数体,否则我们无法保证。
不确定为什么readonly
在这里被提及,但是例如,这可以确保以下场景尽管是愚蠢的,但仍有效:
1
class Base
{
public Base()
{
if (this is Derived) (this as Derived).Go();
}
}
class Derived : Base
{
X x = new X();
public void Go()
{
x.DoSomething(); // !
}
}
2
class Base
{
public Base()
{
Go();
}
public virtual Go() {}
}
class Derived : Base
{
X x = new X();
public override void Go()
{
x.DoSomething(); // !
}
}
此命令在C# Language Specification(17.10.2)中明确说明:
[...]构造函数隐式执行由其类中声明的实例字段的变量初始值设定项指定的初始化。这对应于在进入构造函数之后和直接调用直接基类构造函数之前立即执行的赋值序列。
答案 1 :(得分:1)
必须允许子构造代码调用父代的函数,除非父元素已经完全构造,否则它不能工作。
但是,对象共享相同的内存块。因此,所有内存都是一次性分配的,然后初始化类来处理类层次结构。
答案 2 :(得分:1)
这是一个罕见的地方,对程序方法的理解使得面向对象的方法更容易理解。即使您正在使用OOP,编译器仍然遵循程序逻辑 - 工作从头到尾。
一个简单的例子是当编译器命中private int n = 90
时。首先它为一个整数值分配空间,然后为整数值分配一个标识符,然后为它赋值90.它不能分配值,直到它有两个空间来粘贴它并知道如何访问它,也不能访问不存在的空间。
在这种情况下,派生类Y
构建在基类X
的顶部,类似于变量n
在“类”integer
顶部构建的方式上面的例子。这是由声明class Y:X
触发的 - 编译器甚至无法开始构建Y
,直到它了解如何构建X
。