先看看MSDN上对构造函数的描述:构造函数是一类特殊的方法,用于初始化类型和创建类型的实例。类型构造函数用于初始化类型中的静态数据。类型构造函数由公共语言运行库 (CLR) 在创建类型的任何实例之前调用。类型构造函数是 static(在 Visual Basic 中为 Shared)方法,不能带任何参数。实例构造函数用于创建类型的实例。实例构造函数可以带参数,也可以不带参数。不带任何参数的实例构造函数称为默认构造函数。
先看一个最简单的:
public class Program { static void Main(string[] args) { ClassA a = new ClassA(); Console.ReadKey(); }// end Main. }// end class. class ClassA { public ClassA() { } }这里什么都没做,只是生成了一个ClassA实例,下面是IL反汇编的构造函数的结果:
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // 代码大小 10 (0xa) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: nop IL_0008: nop IL_0009: ret } // end of method ClassA::.ctor
构造函数的反汇编结果名为 .ctor,可以看出虽然我们什么都没做,但是这个函数调用了System.Object类型的构造函数。
看到这里想到什么了吗?ClassA和Object有神马关系?没错,是继承,ClassA默认继承自Object。好吧,再看一个例子:
public class Program { static void Main(string[] args) { ClassA a = new ClassA(); Console.ReadKey(); }// end Main. }// end class. class ClassB //隐式的继承Object { public ClassB() { } } class ClassA : ClassB //继承ClassB { public ClassA() { } }
再看看我们的ClassA的 .ctor :
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // 代码大小 10 (0xa) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void InitializeTest.ClassB::.ctor() IL_0006: nop IL_0007: nop IL_0008: nop IL_0009: ret } // end of method ClassA::.ctor
这次调用了ClassB的构造函数了,那么ClassB呢 :
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // 代码大小 10 (0xa) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: nop IL_0008: nop IL_0009: ret } // end of method ClassB::.ctor
又是call指令,不解释。有兴趣的可以试试更深的继承树,结果应该是一样的
最后来一个总结吧:C#创建实例的时候其构造函数会递归的调用父类的构造函数,直至继承树的根Object。
好吧,当前的讨论范围仅限实例构造函数,而C#中还有一种静态构造函数,不我还是先继续说说非静态的吧。
现在改一改刚才的代码:
public class Program { static void Main(string[] args) { ClassA a = new ClassA(); Console.ReadKey(); }// end Main. }// end class. class ClassA { private int number = 0xabc;//初始化 public ClassA() { } }多了一行字段的声明及初始化,注意构造函数里什么都没写,现在看看A的构造函数的IL代码:
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // 代码大小 21 (0x15) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldc.i4 0xabc IL_0006: stfld int32 InitializeTest.ClassA::number IL_000b: ldarg.0 IL_000c: call instance void [mscorlib]System.Object::.ctor() IL_0011: nop IL_0012: nop IL_0013: nop IL_0014: ret } // end of method ClassA::.ctor
看到那个ldc.i4 0xabc了吧,再看看下一行stfld int32 InitializeTest.ClassA::number,前一行ldc是把一个常数压入栈,后一行stfld指令是从栈中获取值替换实例成员,就是赋值(此处为初始化)。
看来字段的初始化是构造函数的一部分,且最先执行,在递归调用父类构造函数之前。
的把这个代码再改一次:
public class Program { static void Main(string[] args) { ClassA a = new ClassA(); Console.ReadKey(); }// end Main. }// end class. class ClassA { private int number; public ClassA() { this.number = 0xabc;//这次在这里初始化 } }注意看IL代码的变化,和上面对比一下:
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // 代码大小 21 (0x15) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: nop IL_0008: ldarg.0 IL_0009: ldc.i4 0xabc IL_000e: stfld int32 InitializeTest.ClassA::number IL_0013: nop IL_0014: ret } // end of method ClassA::.ctor
class ClassA { private int number; private string str = "A string";//这里加一个字段并赋初值 public ClassA() { Console.WriteLine("ClassA's constructor");//这里可以随便加点语句, 只要编译能过 this.number = 0xabc;//这次在这里初始化 } }IL代码:
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // 代码大小 43 (0x2b) .maxstack 8 IL_0000: ldarg.0 IL_0001: ldstr "A string" IL_0006: stfld string InitializeTest.ClassA::str IL_000b: ldarg.0 IL_000c: call instance void [mscorlib]System.Object::.ctor() IL_0011: nop IL_0012: nop IL_0013: ldstr "ClassA's constructor" IL_0018: call void [mscorlib]System.Console::WriteLine(string) IL_001d: nop IL_001e: ldarg.0 IL_001f: ldc.i4 0xabc IL_0024: stfld int32 InitializeTest.ClassA::number IL_0029: nop IL_002a: ret } // end of method ClassA::.ctor
好吧,大家可能在上一个例子已经看出来了,我想表达的意思就是:大括号内的(我们真正写在构造函数内的)代码是放在最后执行的。
在编译之后的.ctor方法中,我们使用C#编写的语句,即那些可以"看得见的代码"是放在最后执行的,在这之前是调用基类的构造方法,如果在声明字段的时候初始化了,那么初始化的指令在递归地调用构造函数之前执行。
你可能感觉不是那么直观吧,最后这个代码有兴趣可以自己试一试,改一改:
public class Program { static void Main(string[] args) { Console.WriteLine(">>>Test 1:"); ClassA a = new ClassA(); Console.WriteLine(">>>Test 2:"); ClassB b = new ClassB(); Console.WriteLine(">>>Test 3:"); ClassC c = new ClassC(); Console.ReadKey(); /* * * * * * * * * * * * * * * * * * * * * * * * * * Output: * >>>Test 1: * Initialize field, Value: 123 * ClassA's constructor invoking... * >>>Test 2: * Initialize field, Value: 456 * Initialize field, Value: 123 * ClassA's constructor invoking... * ClassB's constructor invoking... * >>>Test 3: * Initialize field, Value: 789 * Initialize field, Value: 456 * Initialize field, Value: 123 * ClassA's constructor invoking... * ClassB's constructor invoking... * ClassC's constructor invoking... * * * * * * * * * * * * * * * * * * * * * * */ }// end Main. //只是为了赋值的时候能直观看到. public static int GetVal(int val) { Console.WriteLine("Initialize field, Value: {0}", val); return val; } }// end class. class ClassA { private int numA = Program.GetVal(123);//调用静态方法而不是直接赋值 public ClassA() { Console.WriteLine(" ClassA's constructor invoking..."); } } class ClassB : ClassA { private int numB = Program.GetVal(456);//调用静态方法而不是直接赋值 public ClassB() { Console.WriteLine(" ClassB's constructor invoking..."); } } class ClassC : ClassB { private int numC = Program.GetVal(789);//调用静态方法而不是直接赋值 public ClassC() { Console.WriteLine(" ClassC's constructor invoking..."); } }