先看看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...");
}
}