zoukankan      html  css  js  c++  java
  • 重温CLR(三)类型基础

    所有类型都从System.Object派生

    “运行时”要求每个类型最终都要从System.Object类型派生。也就是说,一下两个类型的定义完全一致。

    //隐式派生自Object
    class Employee{
        …
    }
    //显式派生自Object
    class Employee: System.Object{
    }

    由于所有类型最终都从System.Object派生,所以每个类型的每个对象都保证了一组最基本的方法。具体地说,System.Object类提供了如表4-1所示的公共实例方法。

     

    此外,从System.Object派生的类型能访问下表的受保护的方法

     

    clr要求所有对象都用new操作符创建。以下代码展示了如何创建一个List对象:

    ModuleItem modulePicc = new ModuleItem(“agent”);

    以下是new操作符所做的事情。

    1、 计算类型及其所有基类型(一直到System.Object,虽然它没有定义自己的实例字段)中定义的所有实例字段需要的字节数。堆上每个对象都需要一些额外的成员,包括“类型对象指针”(type object pointer)和“同步块索引”(synv block index)。clr利用这些成员管理对象。额外成员的字节数要计入对象大小。

    2、 从托管堆中分配类型要求的字节数,从而分配对象的内存,分配的所有字节都设为零(0).

    3、 初始化对象的“类型对象指针”和“同步块索引”成员。

    4、 调用类型的实例构造器,传递在new调用中指定的实参(上例就是字符串“agent”)。大多数编译器都在构造器中自动生成代码来调用基类构造器。每个类型的构造器都负责初始化该类型定义的实例字段。最终调用System.Object的构造器,该构造器什么都不做,简单地返回。

    new执行了所有这些操作之后,返回指向新建对象一个引用(指针)。在前面的实例代码中,该引用保存到变量modulePicc中,后者具有ModuleItem类型

           顺便说一句,没有和new操作符操作符对应的delete操作符;换而言之,没有办法显式释放对象分配的内存。clr采用了垃圾回收机制,能自动检测到一个对象不再被使用或访问,并自动释放对象的内存。


    备注:同步块索引是什么?

    是索引啦。。。。不过这个索引指向哪儿,作用是什么,才是最关键的问题。引用类型对象的同步块索引有这么两个作用:

    1. lock当一个线程lock的时候(即 Monitor.Enter ),该线程会检查参数中的对象的同步块索引, 是否已经有关联的同步块。若没有, CLR就会在全局的SyncBlock数组里找到一个空闲的项,然后将数组的索引赋值给该对象的同步块索引。若存在,则通过同步块索引获取SyncBlock数组的项。然后, 该线程会设置SyncBlock里的内容,标识出已经有一个线程占用了。当有其他线程想lock时,会检查参数的SyncBlock里的内容,发现已经有线程占用了,其他线程就会等待。lock执行完,占用的线程就会释放SyncBlock,其他线程就可以使用了。  

    2. GetHashCode(要说明下同步块索引在32位机器占用32位,高6位作为控制位,表示参与的操作,后26位的含义随着高6位的不同而不同。)GetHashCode时,先通过 ComputeHashCode 方法得出一个哈希值,然后将这个哈希值与其他几个值进行几次或操作,就得到了一个对象的GetHashCode方法返回给我们使用的值。其中参与或操作的一个值,就是同步块索引的后26位的值。而这时同步块索引的高6位代表的含义中就有表示这后26位值用于参加GetHashCode的含义。尽我能力写了,不知道能看明白不。附上链接,里面讲得很详细。

    http://www.cnblogs.com/yuyijq/archive/2009/03/13/1410071.html


    类型转换

           clr最重要的特性之一就是类型安全。在运行时,clr总是知道对象的类型是什么。调用getType方法即可知道对象的确切类型。由于它是非虚方法,所以一个类型不可能伪装成另一个类型。例如,Employee类型类型不能重写GetType方法并返回一个SuperHero类型。

           开发人员经常需要将对象从一种类型转换为另一种类型。clr允许将对象转换为它的(实际)类型或者它的任何基类型。每种编程语言都规定了开发人员具体如何进行这种类型转换操作。例如,c#不要求任何特殊语法即可将对象转换为它的任何基类型,因为向基类型的的转换被认为是一种安全的隐式转换。然后,将对阵转换为它的某个派生类型时,c#要求开发人员只能进行显式转换,因为这种转换可能在运行时失败。一下代码演示了向基类型和派生类型的的转换:

    Object o=new Employee();
    
    Employee e = (Employee)o;

    使用C#的is和as操作符来转换类型

           在c#语言进行类型转换的另一种方式是使用is和as操作符。这里比较常用的是as,因为会减少一次判断,毕竟这种转换还是有性能损失的。

    is用法

    Object o = new ObjectU;
    Boolean bl = (o is Object);   // bl 为 true.
    Boolean b2 = (o is Employee);     // b2 为 false.
    如果对象引用null,is操作符总是返回false。

    as用法

    Employee e= o as Employee;
    if(e!=null){
           //在if语句中使用e
    }

    命名空间和程序集

           命名空间对相关的类型进行逻辑分组,开发人员可通过命名空间方便地定位类型。例如,system.test命名空间定义了执行字符串处理的类型,而System.io命名空间定义了执行i/o操作的类型。

           对于编译器,命名空间的作用就是为类型名称附加以句点分隔的符号,使名称变得更长,更可能具有唯一性。

           重要提示:clr对“命名空间”一无所知。访问类型时,clr需要知道类型的完整名称(可能是相当长的、包含句点符号的名称)以及该类型的定义具体在哪个程序集中,这样“运行时”才能加载正确程序集,找到目标类型,并对其进行操作。

           using指令简化了类型名称,编译器会对当前代码中未识别的类型自动去匹配using 中引用的命名空间,直到找到对应类型。当然也会有潜在问题:可能两个或更多类型在不同命名空间中同名。为了消除歧义,必须显式告诉编译器需要需要生死用哪个类型。

           c# using指令的另一种形式允许为类型或命名空间创建别名。如果只想使用命名空间中的少量代码,不想它的所有类型都跑出来“污染”全局命名空间,别名就显得十分方便。

    using a=wwwwww.asdasd;

    注意:命名空间和程序集不一定相关。特别是,同一个命名空间中的类型可能在不同程序集中实现。同一个程序集也可能包含不同命名空间中的类型,例如system.int32和system.text.stringBuilder类型都在MSCorLib.dll程序集中。

    运行时的相互关系

           本节将解释类型、对象、线程栈和托管堆在运行时的相互关系。此外,还将解释调用静态方法、实例方法和虚方法的区别。

           图4-2展示了已加载clr的一个windows进程。该进程可能有多个线程。线程创建时会分配到1mb的栈。栈空间用于向方法传递实参,方法内部定义的局部变量也在栈上。栈从高位内存地址向低位内存地址构建,图中现在已执行了一些代码,栈上已有一些数据了(栈顶阴影区域)。现在,假定线程执行的代码要调用M1方法。

     

           最简单的方法包含“序幕”(prologue)代码,在方法开始做工作前对其进行初始化;还包含“尾声”(epilogue)代码,在方法做完工作后对其进行清理,以便返回至调用者。M1方法开始执行时,它的“序幕”代码在线程栈上分配局部变量name的内存,如下图。

     

      然后M1调用M2方法,将局部变量name作为实参传递。这造成name局部变量中的地址被压入栈,如图4-4。M2方法内部使用参数变量s标识栈位置(注意:有的cpu架构用寄存器传递实参以提升性能,但这个区别对于当前问题不重要)。另外,调用方法时还会将“返回地址”压入栈,被调用的方法在结束之后应返回至该位置。

     

           M2方法开始执行时,它的“序幕”代码在线程栈中为局部变量length和tally分配内存,如图4-5。然后M2方法内部代码开始执行,最终,M2抵达它的return语句,造成CPU的指令执政被设置成栈中的返回地址,M2的栈展开(unwind)。之后,M1继续执行M2调用之后的代码,M1的栈帧将准确反应M1需要的状态。

     

           最终,M1会返回到它的调用者。

           现在,让我们围绕CLR来调整一下讨论,假如有以下两个类定义:

           windows进程已启动,clr已加载到其中,托管堆已初始化,而且已创建一个线程(连同它的1mb栈空间)。线程已执行了一些代码,马上就要调用M3方法。图4-6展示了目前的状态。

     

           jit编译器将M3的IL代码转换成本机CPU指令时,会注意到M3内部引用的所有类型,这时CLR要确认定义了这些类型的所有程序集都已加载。然后,利用程序集的元数据,clr提取与这些类型有关的信息,创建一些数据结构来表示类型本身。

           稍微讨论一下这些类型对象。堆上的所有对象都包含两个额外成员:类型对象指针(type object pointer)和同步块索引(sync block index)。定义类型时,可在类型内部定义静态数据字段。为这些静态数据字段提供支援的字节在类型对象中分配。每个类型对象最后都包含一个方法表。在方法表中,类型定义的每个方法都有对应的记录项。

           当CLR确认方法需要的所有类型对象都已创建,M3的代码 编译之后,就允许线程执行M3的本机代码。M3的“序幕”代码执行时必须在线程栈中为局部变量分配内存,如图4-8所示。顺便说一句,作为“序幕”代码的一部分,clr自动将所有局部变量初始化为null或者0.然而,如果代码视图访问尚未显式初始化的局部变量,c#会报告错误,使用了未赋值的局部变量。

     

           此外,在调用类型的构造器(本质上是可能修改某些实例数据字段的方法)之前,clr会先初始化同步块索引,并将对象的所有实例字段设为null或者0。new 操作符返回对象的内存地址,该地址保存在变量e中(变量e在线程栈上)。

      M3的下一行代码调用Employee的静态方法Lookup。调用静态方法时,clr会定位与定义静态方法的类型对应的类型对象。然后,jit编译器在类型对象的方法表中查找与被调用方法对应的记录项,对方法进行jit编译(如果需要的话),再调用Jit编译好的代码。由于lookyo方法在堆上构造一个新的manager对象,用joe的信息初始化它,返回该对象的地址。该地址保存到局部变量e中。这个操作结果如图4-10。

           注意,e不再引用第一个manager对象。事实上,由于没有变量引用该对象。所以他是未来垃圾回收的主要目标。

     

           M3的下一行代码调用Employee的非虚实例方法GetYearsEmployed。调用非虚实例方法时,jit编译器会找到与“发出调用的那个变量e的类型”对应的类型对象Employee。如果Employee类型没有定义正在正在调用的那个方法,jit编译器会回溯类层次结构(一直回溯到object,这也是方法覆盖的实现原理),并在沿途的每个类型中查找该方法。之所以能这样回溯,因为每个类型对象都有一个字段引用了它的基类型,这个信息在图中没有显示。

           M3调用Employee的虚实例方法GetProgressReport时,jit编译器要在方法中生成一些额外的的代码;方法每次调用都会执行这些代码。这些代码首先检查发出调用的变量,并跟随地址来到发出调用的对象。变量e当前引用的是代表“jor”的manager对象,所以会调用manager的GetProgressReport实现。

     

           以上我们讨论了源代码、il和jit编译的代码之间的关系。还讨论了线程栈、实参、局部变量以及这些实参和变量如果引用托管堆上的对象。结束本章之前,我们一起探讨下clr内部发生的事情。

           注意Employee和Manageer类型对象都包含“类型对象指针”成员。这是由于类型对象本质上也是对象。clr创建类型对象时,必须初始化这些成员。初始化成什么呢?clr开始在一个进程中运行时,会立即为MSCorLib.dll中定义的system.Type类型创建一个特殊的类型对象。Employee和Manageer类型对象都是该类型的“实例”。因此,他们的类型对象指针成员会初始化成对System.type类型对象的引用。如图4-13

           当然,system.type类型对象本身也是对象,内部也有“类型对象指针”成员。这个指针指向什么?他指向它它而本身,因为system.type类型对象本身是一个类型对象的“实例”。顺便说一句,system.objicet的getType方法返回存储在指定对象的“类型对象指针”成员中的地址。也就是说,getType方法返回指向对象的类型对象的指针。这样就可判断系统中任何对象(包括类型对象本身)的真实类型。

     

  • 相关阅读:
    .NET I/O 学习笔记:目录和文件
    WCF学习笔记(1)——Hello WCF
    《Effective C#》读书笔记——条目10:使用可选参数减少方法重载的数量<C#语言习惯>
    .NET I/O 学习笔记:文件的读和写
    开场白:我关注反洗钱、大家都来关注反洗钱
    关于EOM(Enterprise Operating Model)企业经营模型(12)
    关于EOM(Enterprise Operating Model)企业经营模型(7)
    关于EOM(Enterprise Operating Model)企业经营模型(9)
    关于EOM(Enterprise Operating Model)企业经营模型(10)
    关于EOM(Enterprise Operating Model)企业经营模型(11)
  • 原文地址:https://www.cnblogs.com/qixinbo/p/10278350.html
Copyright © 2011-2022 走看看