所有类型都从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方法返回指向对象的类型对象的指针。这样就可判断系统中任何对象(包括类型对象本身)的真实类型。

重温CLR(三)类型基础的更多相关文章

  1. 重温CLR(五)类型和成员基础

    类型的各种成员 类型可以定义以下种类的成员 1 常量 常量是指出数据值恒定不变的符号.这种符号使代码更易阅读和维护.常量总与类型管理,不与类型的实例管理.常量逻辑上总是静态成员. 2 字段 字段表示只 ...

  2. [CLR via C#]4. 类型基础及类型、对象、栈和堆运行时的相互联系

    原文:[CLR via C#]4. 类型基础及类型.对象.栈和堆运行时的相互联系 CLR要求所有类型最终都要从System.Object派生.也就是所,下面的两个定义是完全相同的, //隐式派生自Sy ...

  3. NET CLR via C#(第4版)第4章 类型基础

    本章内容: 1 所有类型都从System.Object派生 2 类型转换 3 命名空间和程序集 4 运行时的相互关系   本章讲述使用类型和CLR时需掌握的基础知识.具体地说,要讨论所有类型都具有的一 ...

  4. 《C#从现象到本质》读书笔记(三)第3章C#类型基础(下)

    <C#从现象到本质>读书笔记第3章C#类型基础(下) 常量以关键字const修饰.C#支持静态字段(类型字段)和实例字段. 无参属性的get方法不支持参数,而有参属性的get方法支持传入一 ...

  5. [Clr via C#读书笔记]Cp4类型基础

    Cp4类型基础 Object类型 Object是所有类型的基类,有Equals,GetHashCode,ToString,GetType四个公共方法,其中GetHashCode,ToString可以o ...

  6. C#学习笔记——面向对象、面向组件以及类型基础

    C#学习笔记——面向对象.面向组件以及类型基础 目录 一 面向对象与面向组件 二 基元类型与 new 操作 三 值类型与引用类型 四 类型转换 五 相等性与同一性 六 对象哈希码 一 面向对象与面向组 ...

  7. CLR via C# - 基础拾遗

    编译器开关设置 IL代码质量 JIT本地代码质量 /optimize- /debug-(默认设置) 未优化 优化 /optimize- /debug+(full/pdbonly) 未优化 未优化 /o ...

  8. CLR设计类型之接口

    写在前面的话:             写到这一节的时候,CLR设计类型就已经结束了,因为CLR要求的是有一定基础的人看的,所以我们不是从基础类型以及运算符开始的,文章从一开始就讲的是深入面向对象编程 ...

  9. [No0000B5]C# 类型基础 值类型和引用类型 及其 对象判等 深入研究1

    引言 本文之初的目的是讲述设计模式中的 Prototype(原型)模式,但是如果想较清楚地弄明白这个模式,需要了解对象克隆(Object Clone),Clone其实也就是对象复制.复制又分为了浅度复 ...

随机推荐

  1. Android LCD

    Android LCD(一):LCD基本原理篇Android LCD(二):LCD常用接口原理篇Android LCD(三):Samsung LCD接口篇Android LCD(四):LCD驱动调试篇

  2. NET中IL指令详解

    名称 说明 Add 将两个值相加并将结果推送到计算堆栈上. Add.Ovf 将两个整数相加,执行溢出检查,并且将结果推送到计算堆栈上. Add.Ovf.Un 将两个无符号整数值相加,执行溢出检查,并且 ...

  3. Tomcat Connector 参数优化说明

    默认参数 注: Connector 通常在%HOME_TOMCAT%/conf/servser.xml 文件内 # 正常参数 <Connector port=" protocol=&q ...

  4. ASP.NET MVC Bootstrap模板选中菜单高亮显示当前项方法

    当我们处理后台显示当前页面,当前页菜单项高亮,我们可以使用js方法,也可用程序实现,使用Bootstrap模板处理高亮并展开方法之一 1.在项目中导入 <script src="/as ...

  5. 20145219 《Java程序设计》第02周学习总结

    20145219 <Java程序设计>第02周学习总结 教材学习内容总结 类型:基本类型.类类型(参考类型) 基本类型: 整数:short占2字节,int占4字节,long占8字节 字节: ...

  6. Oracle sql plus中常用的几个命令

    1.set linesize 300(表示一行为300个字符) set linesize可以设置一行显示的字符数,默认情况下为80个字符 2.l(list) 可以显示缓冲区中的最后执行的内容 3.ru ...

  7. JUnit4 入门笔记

    Test注解的两个可选参数 expected timeout The Test annotation supports two optional parameters. The first, expe ...

  8. 第十节课-RNN介绍

    2017-08-21 这次的课程介绍了RNN的相关知识: 首先是RNN的几种模型: 分别又不同的应用场景,包括机器翻译,视频的分类... RNN的解释: 主要的特点就是用到了上一个隐含状态的信息,所以 ...

  9. jQuery EasyUI Datagrid VirtualScrollView视图简单分析

    大家都知道EasyUI的Datagrid组件在加载大数据量时的优势并不是很明显,相对于其他一些框架,如果数据量达到几千,便会比较慢,特别是在IE下面.针对这种情况,我们首要做的是要相办法优化datag ...

  10. Sublime Text C# 编译(csharp.sublime-build)

    制作: 1. 配置环境变量PATH C# 7.0 C:\Program Files (x86)\Microsoft Visual Studio\\Enterprise\MSBuild\15.0\Bin ...