理解静态成员(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之间的区别了。

转载于:https://www.cnblogs.com/marsir/p/8811233.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
项目:使用 JavaScript 编写的杀死幽灵游戏(附源代码) 杀死鬼魂游戏是使用 Vanilla JavaScript、CSS 和 HTML 画布开发的简单项目。这款游戏很有趣。玩家必须触摸/杀死游荡的鬼魂才能得分。您必须将鼠标悬停在鬼魂上 - 尽量得分。鬼魂在眨眼间不断从一个地方移动到另一个地方。您必须在 1 分钟内尽可能多地杀死鬼魂。 游戏制作 这个游戏项目只是用 HTML 画布、CSS 和 JavaScript 编写的。说到这个游戏的特点,用户必须触摸/杀死游荡的幽灵才能得分。游戏会根据你杀死的幽灵数量来记录你的总分。你必须将鼠标悬停在幽灵上——尽量得分。你必须在 1 分钟内尽可能多地杀死幽灵。游戏还会显示最高排名分数,如果你成功击败它,该分数会在游戏结束屏幕上更新。 该游戏包含大量的 javascript 以确保游戏正常运行。 如何运行该项目? 要运行此游戏,您不需要任何类型的本地服务器,但需要浏览器。我们建议您使用现代浏览器,如 Google Chrome 和 Mozilla Firefox。要玩游戏,首先,单击 index.html 文件在浏览器中打开游戏。 演示: 该项目为国外大神项目,可以作为毕业设计的项目,也可以作为大作业项目,不用担心代码重复,设计重复等,如果需要对项目进行修改,需要具备一定基础知识。 注意:如果装有360等杀毒软件,可能会出现误报的情况,源码本身并无病毒,使用源码时可以关闭360,或者添加信任。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值