首页 > 代码库 > CLR - 设计类型

CLR - 设计类型

前言

  好记性不如料“笔头”系列。。。

  类型基础

  基元类型、引用类型和值类型

类型基础

“运行时”要求每个类型最终都从System.Object 类型派生。

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

公共方法名称 说明
Equals 如果两个对象具有相同的值,就返回true。
GetHashCode 返回对象的值的一个哈希码。
ToString

该方法默认返回类型的完整名称(this.GetType().FullName)。然而,我们经常需要重写这个方法,使它返回一个String 对象,其中包含对象状态的一个表示。例如,核心类型(比如Boolean 和Int32)重写了这个方法,返回它们的值的一个字符串表示。

GetType 返回从Type 派生的一个对象的实例,指出调用GetType 的那个对象是什么类型。返回的Type 对象可以和反射类配合使用,从而获取与对象的类型有关的元数据信息。

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

受保护的方法名称 说明
MemberwiseClone 这个非虚方法能创建类型的一个新实例,并将新对象的实例字段设与this 对象的实例字段完全一致。返回的是对照新实例的一个引用。
Finalize 在垃圾回收器判断对象应该被作为垃圾收集之后,在对象的内存被实际回收之前,会调用这个虚方法。需要在回收之前执行一些清理工作的类型应该重写这个方法。

  要求所有对象都用new 操作符来创建。下面这行代码展示了如何创建一个class1 对象:

  Program p = new Program("info");

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

  1:计算类型及其所有基类型(一直至System.Object)中定义的所有实例字段需要的字节数。堆上的每个对象都需要一些额外的成员——即”类型对象指针“(type object pointer)和“同步块索引”(sync block index)来由CLR 用于管理对象,这些额外成员的字节数会计入对象大小。

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

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

  4:调用类型的实例构造器,向其传入在对new 的调用中指定的任何实参(上例就是字符串"info")。大多数编译器都在构造器中自动生成代码来调用一个基类构造器。每个类型的构造器在调用时,都要负责初始化由这个类型定义的实例字段。最终调用的是System.Object 的构造器,该构造器只是简单地返回,不会做其他任何事情。

CLR 允许将一个对象转换为它的(实际)类型或者它的任何基类型。

  在C# 中进行类型转换可以使用is 与as 操作符。

  is 操作符检查一个对象是否兼容于指定的类型,并返回一个Boolean 类型:true 或false。注意,is 操作符永远不会抛出异常。

  Object o = new Object();

  Boolean b1 = (o is Object); //返回true

  as 操作符检查一个对象是否兼容于指定的类型,如果兼容,as 会返回对同一个对象的一个非null 引用,反之,则返回null。

  Object o = new Object();

  string str= o as string;//返回null

类型、对象、线程栈、托管堆在运行时的相互联系

  运行程序时,会启动一个进程,每个进程最少有一个线程。一个线程被创建时会分配到1MB 大小的栈。这个栈的空间用于向方法传递实参,并用于方法内部定义的变量。

  现在,Windows 进程已经启动,CLR 已经加载到其中,托管堆已初始化,而且已创建一个线程(连同它的 1MB 栈空间)。现在已经进入 Main() 方法,马上就要执行 Main 中的语句,所以栈和堆的状态如下图所示:

  当JIT 编译器将Main() 方法的IL代码转换成本地CPU 指令时,会注意到其内部引用的类型。这个时候,CLR 要确保定义了这些类型的所有程序集都已加载。然后利用程序集的元数据,CLR 提取与这些类型有关的信息,并创建一些数据结构来表示类型本身。在线程,会创建所需要的所有对象。下图显示了在Main 被调用时,创建类型对象后的状态:

  当CLR 确定方法需要的所有类型对象都已创建,而且Main 方法的代码编译之后,就允许线程开始执行Main 的本地代码。Main 的“序幕”代码执行时,必须在线程栈中为局部变量分配内存。同时作为“序幕”代码的一部分,CLR 会自动将所有变量初始化为null 或0(零)。

  然后Main 执行它的代码来构造一个Person 对象。这造成在托管堆中创建Person 类型的一个实例(也就是一个Person 对象),同时包含类型及其所有基类型(一直至System.Object)中定义的所有实例字段与同步块索引、类型对象指针。CLR 会自动先初始化同步块索引,并将对象的所有实例字段设为null 或0(零),再调用类型的构造器,最后返回Person 对象的内存地址,该地址保存在变量p 中。

  下一步Main 执行到ToString 方法。Jit 编译器在Person 类型对象的方法表中查找引用了被调用方法的记录项,对方法进行JIT 编译(如果还没有编译过),再调用JIT 编译的代码。

  这时。可以看到Person 类型对象中也包括类型对象指针,这是因为类型对象本质上也是对象。CLR 创建类型对象时,必须初始化这些成员。CLR 开始在一个进程中运行时,会立即为MSCorLib.dll 中定义的System.Type 类型创建一个特殊的类型对象。Person 类型对象都是System.Type 类型对象的一个“实例”。因此,Person 类型对象指针成员会初始化成对System.Type类型对象的引用。因System.Type 类型对象本身也是一个对象,内部的“类型对象指针”成员会指向它本身。