所有类型都从System.Object派生

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

  1. //隐式派生自Object
  2. class Employee


  3. //显式派生自Object
  4. class Employee: System.Object{
  5. }

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

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

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

  1. 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#要求开发人员只能进行显式转换,因为这种转换可能在运行时失败。一下代码演示了向基类型和派生类型的的转换:

  1. Object o=new Employee();
  2.  
  3. Employee e = (Employee)o;

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

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

is用法

  1. Object o = new ObjectU;
  2. Boolean bl = (o is Object); // bl 为 true.
  3. Boolean b2 = (o is Employee); // b2 为 false.
  4. 如果对象引用nullis操作符总是返回false

as用法

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

命名空间和程序集

命名空间对相关的类型进行逻辑分组,开发人员可通过命名空间方便地定位类型。例如,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. HDU4631(标程代码)

    /*将x从小到大排序,每次插入一个点,直接找比这个点的x大的第一个,然后从这个开始向两边找 ,找点的下标用多重容器实现*/ #include<stdio.h> #include<st ...

  2. 【JavaScript】用JS绘制一个球

    参考: 1.http://www.w3school.com.cn/html5/html_5_canvas.asp 2.http://blog.csdn.net/qq_27626333/article/ ...

  3. 微信小程序快速开发

    微信小程序快速开发 一.注册小程序账号,下载IDE 1.官网注册https://mp.weixin.qq.com/,并下载IDE. 2.官方文档一向都是最好的学习资料. 注意:1)注册账号之后会有一个 ...

  4. NOIP Mayan游戏

    描述 Mayan puzzle是最近流行起来的一个游戏.游戏界面是一个7行5列的棋盘,上面堆放着一些方块,方块不能悬空堆放,即方块必须放在最下面一行,或者放在其他方块之上.游戏通关是指在规定的步数内消 ...

  5. ls存在的文件,不能操作

    bash-4.2# pwd/oracle/product/10.2.0/db_1/network/adminbash-4.2# lssqlnet.ora                 libnrad ...

  6. jQuery和javaScript页面加载完成时触发的事件

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. jsp——js事件修改属性样式的两种方法(直接赋值、修改属性)、验证表单符合某要求、阻止表单提交、告诉浏览器不要缓存

    代码 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncodi ...

  8. Java多线程 - 线程同步

    多线程操作同一个对象时,容易引发线程安全问题.为了解决线程安全问题,Java多线程引入了同步监视器. 同步代码块 同步代码块语法格式如下: synchronized(obj){ //此处的代码即为同步 ...

  9. JDK__下载地址

    1. http://www.oracle.com/technetwork/java/archive-139210.html ZC: 貌似 从JDK7开始,有for ARM的版本,类似 : “Linux ...

  10. JNI_Z_07_方法的操作(没有String类型的参数)_参数的传递方式

    1. 2.VC6(CPP)的DLL代码: #include<stdio.h> #include "jniZ_TjniMethod02.h" JNIEXPORT void ...