C#对象诞生记之构造函数都干了啥

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

继承树是 A<-B<-C,字段初始化的顺序是CBA,而构造函数中的代码的执行顺序是ABC.




你可能感兴趣的:(.Net,C#,c#,C#,初始化,构造函数)