先看代码:
namespace Temp { class Program { static void Main(string[] args) { Class1 c = new Class1(); } } class BaseClass { int z = 3; public BaseClass() { MethodA(); } public virtual void MethodA() { Console.WriteLine("BaseClass.MethodA"); } } class Class1 : BaseClass { int x = 1; int y; public Class1() { y = 2; } public override void MethodA() { Console.WriteLine(x + y); } } }
以上是一个简单的继承层次结构。不要使用 VS 测试,脑子分析一下最终输出了什么?
分析过程中脑子存在任何疑问的同学,请马上动手测试一下吧,在 Main 方法中设个断点单步跟踪一下。
这里描述一下单步调试的整个过程:
×××光标进入 Class1 类时跳入了第一句 int x = 1;
×××光标跳过第二句 int y 指向 Class1 的构造函数;
在执行构造函数的代码块之前跳入了父类,×××光标指向父类 BaseClass 的 int z = 3 语句;
×××光标指向 BaseClass 的构造函数;
×××光标指向构造函数内的 MethodA() 调用;
×××光标跳向子类 Class1 重写的方法 MethodA();
查看两个字段发现 x=1, y=0;
×××光标指向 Console 语句;
×××光标从父类构造函数的 MethodA() 调用中跳出;
×××光标从父类构造函数跳出,并再次指向子类构造函数,执行完其中的代码块;
直至执行完毕。


这里说明了几个顺序问题:
对于直接赋值的字段的赋值步骤是在构造函数之前执行,子类字段的赋值是在父类的字段赋值之前;
对于字段的内存分配、初始化等步骤是在我们所能看到的×××光标进入 Class1 类的步骤之前;
执行构造函数前会首先执行父类的构造函数;
执行构造函数时 CLR 已能识别方法的覆写情况,表明方法的加载过程是在对字段的赋值步骤之前;
int 类型的字段在分配内存、初始化阶段已默认赋了 0 值(仅限字段中的 int,方法中的int变量并非如此)。

总结:当执行 new 语句时,发生了以下几件事情(更细的情形本文暂不探讨):
为字段分配内存并进行初始化;
如果类是第一次加载(即系统中从未创建类的其它对象,或者曾经创建但因不再有引用而被 GC 全部回收),则 Copy 其实例方法至方法表;
为类中需要直接赋值的字段赋值;
执行构造函数。

这里只分析了字段类型为 int 的情形,欢迎大家自己探索 string 或其它引用类型的情形。欢迎多多指教。
PS: 请教一下,VS中单步调试时的×××光标有没有专用术语?