CLR via C#深解笔记二 - 类型设计

类型基础
全部类型都从System.Object派生
 
CLR要求全部对象都用new 操做符来建立。
Employee e = new Employee("Constructor Parameters");
 
如下是 new 操做符所作的事情:
#1, 计算类型及全部基类型(一直到System.Object, 虽然它没有定义本身的实例字段)中定义的全部实例字段须要的字节数。
堆上的每一个对象还须要一些额外(overhead 开销成员)的成员 -- 即“类型对象指针”(type object pointer)和“同步块索引”(sync block index)。这些成员由CLR用于管理对象。这些额外成员的字节数会计入对象大小。
#2, 它从托管堆中分配指定类型要求的字节数,从而分配对象的内存,分配的全部字节都设为0。
#3, 它初始化对象的“类型对象指针”和“同步块索引”成员。
#4, 调用类型的实例构造器,向其传入在对new 的调用中指定的任何实参。大多数编译器都在构造器中自动生成代码来调用一个基类构造器。每一个类型的构造器在调用时,都要负责初始化由这个类型定义的实例字段。最总调用的是System.Object的构造器,该构造器只是简单地返回,不会作其余任何事情。
new执行了全部操做以后,会返回指向新建对象一个引用(或指针)。在前面的示例代码中,这个引用会保存到变量e中,后者具备Employee类型。
 
类的“新实例”和“实例成员”:两种不一样的“实例”。一种是类的实例,也就是具体的对象。另外一种是类中定义的实例字段。所谓“实例字段”,就是指非静态字段,有时也称为“实例成员”。简单地说,实例成员是属于类的对象的,
而静态成员是属于类的。
 
类型安全
CLR老是知道一个对象(某个类型的实例)是什么类型。全部表达式都解析成某个类型的实例,在编译器生成的代码中,只会执行对这个类型来讲有效的操做。与非类型安全的语言相比,类型安全的语言的优点在于:程序员会犯的许多错误能在编译时检测到,确保代码在你的尝试执行它以前是正确的。除此以外,编译时语言一般能生成更小、更快的代码,由于他们能在编译时进行更多的假设,并在生成的IL和元数据中落实那些假设。
 
类型转换
CLR最重要的特性之一就是类型安全性。在运行时,CLR老是知道一个对象是什么类型。调用GetType() 方法,老是知道一个对象确切的类型是什么。
开发中,开发人员会常常将一个对象从一种类型转换为其余各类类型。CLR容许将一个对象转换为它的(实际)类型或者它的任何基类型。
#1: 向基类型的转换被认为是一种安全的隐式转换。
#2: 将对象转换为它的某个派生类型时,C#要求只能进行显式转换,由于这样的转换有可能在运行时失败。在运行时,CLR检查类型操做,肯定老是转换为对象的实际类型或者它的任何基类型。
 
这就是类型安全的设计。若是CLR容许这样的转型,就无类型安全性可言了,将出现难以预料的结果 -- 其中包括应用程序崩溃,以及安全漏洞的出现(由于一种类型能轻松地假装成另外一种类型)。
类型假装是许多安全漏洞的根源,它还会破坏应用程序的稳定性和健壮性。类型安全是CLR一个重要的目标。
 
is 操做符,检查一个对象是否兼容于指定的类型,并返回一个Boolean值;true或false。注意 is 操做符永远不会抛出异常。若是对象引用是null,is操做符老是返回false。
 
Object o = new Object();
if (o is Employee)
{
     Employee e = (Employee)o;
}
 
这段代码中,CLR实际上会检查两次对象的类型。
CLR 的类型检查加强了安全性,但无疑也会对性能形成必定影响。由于CLR首先必须判断变量引用的对象的实际类型。而后,CLR必须遍历继承层次结构,用每一个基类型去核对指定的类型(如Employee)。
上面这个事一个至关经常使用的编程模式,因此C#专门提供了as操做符,目的就是简化这种代码的写法,同时提高性能。
 
Employee e = o as Employee;
if(e != null)
{
     //.....
}
 
as 操做符的工做方式与强制类型转换同样,只是它永远不会抛出一个异常 -- 相反,若是对象不能转型,结果就是null。因此,正确作法也就是检查最终生成的引用是否为null。应该不要直接使用最终生成的引用,不然可能会抛出一个System.NullReferenceException 异常。
 
注意:C#容许在一个类型中定义转换操做符方法。只有在使用一个转型表达式时,才会调用这些方法;使用C#的as或者is操做符时,永远不会调用他们。
 
命名空间 (namespace)
用于对相关的类型进行逻辑性分组,开发人员可使用命名空间方便地定位一个类型。例如,System.Text命名空间定义了一组执行字符串处理的类型。
using 指令指示编译器为每个类型附加不一样的前缀,直到找到一个匹配项。using的使用,不只极大地减小打字量,还有助于加强代码的可读性。
using指令还支持另外一种形式,容许为一个类型或者命名空间建立别名。若是只想使用一个命名空间中的少数几个类型,不但愿它的全部类型都跑出来“污染”全局命名空间,别名就显得十分方便。
 
using System;
using jack = CSI.Widget;
 
重要提示:CLR并不知道命名空间的任何事情。访问一个类型时,CLR须要直到类型的完整名称(多是一个至关长的、包含句点符号的名称)以及该类型的定义具体在哪个程序集中。这样一来,“运行时”才能加载正确的程序集,找到目标类型,并对其进行操做。
 
编译器会扫描引用的全部程序集,在其中查找类型的定义。一旦找到正确的程序集,程序集信息和类型信息就会嵌入最终生成的托管模块的元数据中。为了获取程序集信息,必须将定义了“引用的类型”的程序集传给编译器。
默认状况下,C#编译器会自动在MSCorLib.dll 程序集中查找 “引用的类型”,即便你没有显式告诉它这样作。MSCorLib.dll程序集中包含了全部核心Framework类库(FCL)类型的定义,好比Object, Int32, String等。
 
命名空间和程序集(实现了一个类型的文件)不必定是相关的。特别是,同一个命名空间中的各个类型多是在不一样的程序集中实现的。在一个程序集中,也可能包含不一样命名空间中的类型。
 
运行时的相互关系
 
类型、对象、线程栈和托管堆在运行时的相互关系。调用静态方法、实例方法和虚方法的区别。
 
 
已经加载了CLR的一个Microsoft Windows 进程。这个进程中,可能存在多个线程。一个线程的建立时,会分配到一个1MB大小的栈。这个栈的空间用于向方法传递实参,并用于方法内部定义的局部变量。栈是从高位内存地址向低位内存地址构建的。
 
 
栈帧(stack frame)表明的是当前线程的调用栈中的一个方法调用。在执行线程的过程当中进行的每一个方法的调用都会在调用栈中建立并压入一个stack frame
 
   
 
 
 
 
 
 
 
至此咱们讨论了源代码、IL和JIT编译的代码之间的关系,还讨论了线程栈、实参、局部变量以及这些实参和变量如何引用托管堆上的对象。我知道了,对象中包含一个指针,它指向对象的类型对象(类型对象中包含静态字段和方发表)。
还讨论了JIT编译器如何决定静态方法、非虚实例方法以及虚实例方法的调用方式。这一切的理解,能够帮助深入地认识CLR的工做方式。
 
注意,Employee和Manager类型对象都包含“类型对象指针”成员。这是因为类型对象本质上也是对象。CLR建立类型对象时,必须初始化这些成员。初始化成什么呢?CLR开始在一个进程中运行时,会当即为MSCorLib.dll中定义的System.Type类型建立一个特殊的类型对象。Employee和Manager 类型对象都是该类型的“实例”。所以,它们的类型对象指针成员会初始化成对System.Type类型对象的引用,以下面所示。
 
 
-----------------------------------------------------------
 
相关文章
相关标签/搜索