理解静态成员(static)的本质---类型对象

我们只知道静态成员的用法,比如调用一个静态方法要用类名去点出来,比如Math.Max(),但是为什么是这样调用呢?为什么静态构造函数不能有访问修饰符?为什么静态构造函数没有参数?静态函数的执行时间又是什么时候?为什么?

在面向对象中,万物皆对象。其中就有一种对象叫作类型对象,类型对象就是我们创建一个class时所代表的对象。

    class Program
    {
        public static string name;//静态字段 
        public string tag;//实例字段 
        static void Main(string[] args)
        {
            Program p = new Program();//Program是类型对象,p是这个类型对象的实例对象
        }
    }

在C#中,每个AppDomain都保证了一个类型对象的唯一性(原理后面再解释)。

类型对象是实例对象的模板,具体来说,就是运用new操作符实例化一个对象的时候,CLR首先会计算类型对象中定义的实例字段所需要的字节数,然后会为对象分配两个额外的字段(类型对象指针和同步块索引)所需要的字节(顺便说下,C#中每个对象都有两个额外的字段),最后返回对象的引用。其中实例对象的类型对象指针指向的就是类型对象。

既然说了所有对象都有两个额外的字段,那么类型对象显然也是有的,类型对象指向的是Type类型,即所有的类型对象都相当于是Type类型对象的实例。而Type类型的类型对象指针指向的是它本身。这也就理解了下面这个代码的意义。

 Type t1 = p.GetType();
 Type t2 = typeof(Program);

同时也可以利用类型对象指针去思考下虚方法的调用原理。

那么类型对象什么时候初始化呢?它又是用什么来初始化呢?

要创建一个类型的实例或者调用类型对象的成员的时候,才需要初始化类型对象。因为毕竟只有有了模块才能实例化出来,有了对象才能使用它的成员,所有的静态成员都是属于类型对象而不属于类型的实例,所以调用的时候用的是类名直接点出来。就像类型的实例方法,是用类型的实例名点出来一样。在C#中,对象的初始化几乎都是通过调用构造函数来实现的(object的MemberwiseClone,以及反序列化除外)。要初始化一个类型对象必须调用类型对象的构造函数,它就是我们所知道的静态构造函数(类型构造器)。类型的静态构造函数是用来初始化类型对象的,而实例构造函数是用来初始化类型的实例对象的。

程序运行时,我们无法创建类型对象,类型对象的创建都是由CLR来执行的,所以静态构造函数是没有访问修饰符的,即它永远是private。因此它也没有参数,即使有,也没有什么意义,因为都是由CLR执行的,所以类型构造器是没有参数且不能有访问修饰符。在程序集的清单元数据文件中,记录了若干个表,其中就有方法定义表,大致就是记录方法的名称、标志、索引等。在CLR编译一个方法的时候,它会从元数据中看下这个方法中都引用了哪些类型,然后检查当前的AppDomain中是否已经执行了这个类型的构造器,如果没有执行,那么就执行它。当然方法可能被多个线程同时访问,所以调用静态构造函数的时候,调用线程会获得一个互斥锁,其它线程等待,当调用线程执行完成以后就会,其它线程再次进入方法时发现方法已经被执行了就会直接返回。

这就是类型对象。

其实泛型类也是类型对象。而且由泛型类型创建出的类型对象也是分别独立的。

namespace Static
{
    class Program
    {
        static void Main(string[] args)
        {
            GenericType<int>.name = "Close Type";
           // Console.WriteLine(GenericType<>.name);               //C#不支持开放类型,所以会报错
            Console.WriteLine(GenericType<int>.name);              //打印结果 : Close Type
            Console.WriteLine(GenericType<float>.name);            //打印结果 : Open Type
            Console.ReadKey();
        }
    }
    class GenericType<T>                                           //GenericType<>类型对象
    {
        public static string name = "Open Type";
    }
}

 可以看到GenericType<int>、GenericType<float>、GenericType<>是不同的类型对象。所以现在更好理解const、readonly、static readonly之间的区别了。

posted @ 2018-04-12 23:32  Marsir  阅读(1498)  评论(0编辑  收藏  举报