多态编程的CLR探索

我们知道在定义类字段的时候可以对其初始化,如果子类和父类都有多个字段需要初始化,那么初始化的顺序如何呢?

实例化字段

测试代码如下:

class A
{
    public int m = 5;
}

class B : A
{
    public int n = 8;
    public B(int v)
    {
        n = v;
    }
}

static void Main(string[] args)
{
    B a = new B(10);
}

使用ildasm工具查看生成的B类构造函数的IL代码

可以看到,在生成的B类构造函数中,先初始化了自己的字段,然后调用基类构造函数,最后执行构造函数剩余部分代码,而在执行基类B的构造函数时同样先初始化类B本身的字段,然后调用基类object的构造函数,接着执行B类构造函数的剩余部分。

因此我们可以得出结论:

当创建对象时,CLR会自动调用类的构造函数,在此构造函数中初始化自身字段和调用基类的构造函数,这是一个递归的过程,一致递归到.Net最顶层基类Object的构造函数,然后再返回。

静态字段

静态字段的初始化与实例字段略有不同,测试代码如下:

class A
{
   public static int m = 5;
}

class B : A
{
  public static int n = 8;
}

使用ildasm工具查看

c#编译器自动生成了一个.cctor方法,完成的功能是初始化自身所在类型的静态字段,但它不会调用基类的cctor方法。cctor方法称为'类型构造器",或者叫"静态构造函数",c#编译器会为每一个拥有静态成员的类型生成一个类型构造器方法,例中字段n如果不在定义的同时初始化,也可以在静态构造函数中初始化,代码如下,c#编译器会为静态构造函数生成一个.cctor方法,因此我们可以说:类的静态构造函数用于对静态字段进行初始化,它不会自动调用基类的静态构造函数

class B : A
{
    public static int n;
    static B()//静态构造函数
    {
        n = 8;
    }
}

实例字段的内存布局

测试代码如下:

class parent
{
   public int pD = 100;
}

class Child : parent
{
   public int cD = 200;
}
Child objChild = new Child();

创建完对象之后,内存布局如下图所示

静态字段的内存布局

测试代码如下:

class parent
{
    public static int pS = 100;
}
class Child : parent
{
    public static int cS = 200;
    public static void VisitStaticField()
    {
        cS += pS;   //访问静态字段
    }
}

上述代码对应的内存布局图如下

可以看到静态方法和静态字段都存在于方法表中,子类方法表有一个指针与父类方法表联系起来,Child类的基类型方法表指针指向Parent方法表,而Parent方法表的基类型方法表指针指向Object方法表,由于Object是最顶层的基类,所以其基类型方法表指针为空。

虽然子类和基类方法表通过指针相联,但子类中的静态方法访问父类的静态字段并不需要使用此指针,这是因为c#编译器会将相应的代码编译为ldsfld或stsfld指令,在这些指令中直接定位到了相应的类型方法表,不需要在程序运行时“临时”通过类型方法表指针去找。

这些相关联的方法表,能够为程序运行时的类型动态转换提供方便。因此CLR通过类型方法表知道某个类型的父类和子类是什么,从而对能否将此类型转换为特定类型作出判断。

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章