欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-04-08 16:47:59
我们只知道静态成员的用法,比如调用一个静态方法要用类名去点出来,比如Math.Max(),但是为什么是这样调用呢?为什么静态构造函数不能有访问修饰符?为什么静态构造函数没有参数?静态函数的执行时间又是什么时候?为什么? 在面向对象中,万物皆对象。其中就有一种对象叫作类型对象,类型对象就是我们创建一个 ......

我们只知道静态成员的用法,比如调用一个静态方法要用类名去点出来,比如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#中每个对象都有两个额外的字段),最后返回对象的引用。其中实例对象的类型对象指针指向的就是类型对象。

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

既然说了所有对象都有两个额外的字段,那么类型对象显然也是有的,类型对象指向的是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之间的区别了。