.NET大局观之CLR
了解.NET,首先还是由CLR开始吧。真是惭愧,今天看书只看了那么一点点,那就先来这点,现学现卖了。
先来个英文扫盲吧(本人是英文盲)。。。
1、managed objects 托管对象(可以由不同语言编写)
2、GC Garbage Colletion 垃圾回收
3、DLL Dynamic Link Libraries 动态链接库
4、metadata 元数据
5、managed code 托管代码
6、MSIL Microsoft Intermediate Language 中间语言
7、native code 本地代码
8、JIT just-in-time compilation 即时编译
9、CTS Common Type System 通用类型系统
10、instance 实例
11、stack 堆栈
12、heap 堆
CLR都提供哪些服务呢?
1、managed objects
2、GC
3、DLL
4、metadata
编译和运行托管代码的过程是怎样的呢?
简单的说是这样的
源代码-》语言编译器-》MSIL和元数据-》JIT编译器-》本地代码
其中前三个步骤属于编译过程,后两个步骤属于执行过程。
如何编写托管代码呢?
CLR提供了一个通用类型系统,也就是CTS(Common Type System),它
只定义了核心语义,但没有定义语法,这样就可以使用不通的语言定义语法而使用这
个通用的语义,这就是为什么可以使用多种不同语言基于CLR编写managed
objects了。
CTS的每一个类型都继承自Object类型,完整名称是System.Object,它包含
两大类,一个是引用类型(reference type),一个是值类型(value type)。引
用类型的实例是一个引用,就像它的名字一样,指向的是该类型的一个实
值,继承自Object;而一个值类型的实例则内含实值本身;继承自ValueType,
ValueType又继承自Object。
值类型和引用类型的根本差异在于,值类型被分配与堆栈,而引用类型只不过是
一个位于栈的引用,指向一个被分配的堆的实值。如下图
一个引用类型也可能包含一个值类型,所以呢,一些值类型的实例也有可能在堆上。
下面简单介绍一下值类型和引用类型
1、值类型
首先值类型都是密封的(sealed),也就是说,值类型不能被继承,那么值类型
都分哪些呢?
(1)、Byte:一个8位的不带正负号的整数。
(2)、Char:一个16位的Unicode字符。
(3)、Int16、Int32、Int64:带正负号的16位、32位、64位整数。
(4)、UInt16、UInt32、UInt64:不带正负号的16位、32位、64位整数。
(5)、Single和Double:单精度(32位)和双精度(64位)浮点数。
(6)、Decimal:96位十进制数。
(7)、Enum:继承自System.Enum类型,是“一群整数数据”,而不是个
数值。
(8)、Boolean:布尔值。
2、引用类型
(1)、Class(类):类可以包含方法、属性、事件。可以被继承,但是是单一
继承而非多重继承。如果类被标记为sealed(密封的),那么当然就不能继承了。如
果类标记为abstract,那么就不能被实例化,而只能作为其他类的基类使用。类还可
以标记为public,private等。
(2)、Interface(接口):接口可以包含方法、属性、事件。接口是可以多重
继承的。但是接口并不实现任何东西,只是提供一种将类型定义聚合在一起的方式,
具体实现就留给支持接口的类型了,如继承接口的class。
(3)、Array(数组):继承自System.Array类型。是一组具有相同类型的
值。可以是一维或多维,上限和下限都可以修改。
(4)、Delegate(委托):继承自System.Delegate类型。是指向一个方法
的安全可靠的指针。通常用于事件的处理和回调(callbacks)。
说完了值类型和引用类型的基本知识,就该学习装箱(boxing)和拆箱
(unboxing)了。
什么是装箱呢?可以归纳为一句话“将值类型转换为引用类型”,就是装箱。
那么拆箱呢?反过来说就是了。。。下图是装箱的图示
CLS(Common Lanage Specification): 通用语言规范
CLR定义了一套巨大而且复杂的类型,但是并不是所有的类型对所有的语言都是
有作用的。而CLR的目标就是允许某种语言的代码可以调用另外一种语言编写的代
码,这个时候该怎么办呢?CLS就解决了这个问题。CLS定义了一个庞大的CTS子
集,任何的语言如果想和其他CLS语言进行互操作,就必须遵从它。比如,CLS要求
支持Boolean、Byte、Int32、Char,等等,但它并不要求支持UInt32、
UInt16。
Compiling Managed Code 编译托管代码
前面说过了,编译时会产生两样东西,一个是MISL,一个是元数据。如下图
下面就来说说MISL和元数据吧。
1、MISL
MISL究竟是什么呢?其实它和处理器的本地指令集非常相似。不过它不需要硬
件支持。MSIL运行前总是被编译成目标处理器的本地代码。CLR定义的是一个基于
堆栈的虚拟机(virtual machine),类似于java的虚拟机吧。那么来看看MSIL的一
些指令,是不是和本地指令集的指令很相似呢?
(1)、add:将堆栈最顶端的两个值相加,并将结果压回堆栈顶端。
(2)、box:装箱。
(3)、unbox:拆箱
(4)、call:回调方法。
(5)、newobj:创建一个新对象,或创建一个值类型实例。
......
事实上,MSIL是CLR的汇编语言,就这么理解吧。
2、元数据
元数据是干什么的呢?它是“用以描述它所关联的托管代码所定义的类型”,那
么它存储在哪呢?“从那些类型生成的MSIL的相同文件中”,比较难理解。。。看
下图吧
那么元数据都包含了些什么东西呢?说几个主要的吧
(1)、类型的名称;
(2)、类型的可见性,可为public,也可为assembly;
(3)、这个类型所实现的任何方法;
(4)、这个类型所暴露的任何属性(property);
(5)、这个类型所提供的任何事件(event);
还包括一些其他诸如方法的参数信息等等。
Organizing Managed Code:Assenbilied
组织托管代码:程序集
程序集由一个或多个文件组成,这些文件构成了一个逻辑单元,而每个程序集都会有一份程序集清单(Manifests)。程序集可以由一个文件或多个文件组成。对
以“单文件程序集”,清单存储于文件自身;而对于一个“多文件程序集”,清单存储在程序集的某个文件里。不管是单一还是多个,清单描述整个程序集信息。清
单里到底都描述了哪些信息呢?
1、程序集名称(还可以有强名称);
2、程序集版本号:形式如下:<主版本号>.<次版本号>.<内部版本号>.<修订号>,如1.2.1327.942,注意,每个程序集都有版本号。
3、程序集的文化背景:指明该程序集所支持的语言和文化。
4、程序集所包含的所有文件列表,以及根据这些文件计算出来的一个散列值(hash value)
5、程序集所依赖的其他程序集,以及每一个被依赖的程序集的版本号。
大多数的程序集都是只包含单一DLL的。即使它包含的是多个文件,程序集总是一个逻辑上不可分割的单元。程序集分哪些类呢?分类方式最常见的是分为静态程
序集和动态程序集。静态程序集由诸如Visual Studio这样的工具生成,内容被存储在磁盘上;动态程序集直接在内存中生成,而后就可以立即运行。一旦创建完毕,
动态程序集便可保存于磁盘,以后可在此加载并运行。
Excuting Managed Code 运行托管代码
1、装载程序集
要运行程序集,首先要找到它,并把它装入内存。那么是如何查找这个程序集的呢?下面是一个简单的过程,实际上是很复杂的。
首先根据配置文件来确定程序集版本,然后在GAC-global assembly cache(全局程序集缓存区,这个缓存区只能放置拥有强名称的程序集)中查找该程序集。
不在GAC中?不要紧,它将继续检查应用程序的某个配置文件去搜索程序集的位置并载入内存,如果还是没有找到,那将继续通过其他途径进行查找,直到找到为
止。那么如果最终结果还是没找到呢?显而易见,加载程序集失败,程序就不能运行了。
2、Compiling MSIL 编译MSIL
编译器如果生成托管代码,总是会产生MSIL,但是MSIL不能被任何真实的处理器运行,所以就需要编译成本地代码去运行。如何编译呢?又分为JIT(即时编
译)和创建本地映像。
(1)、JIT编译
就是让CLR先装载程序集,然后在每个方法第一次调用时再编译它。就跟它的名字一样,调用时才编译,所以是即时编译。图6
(2)、创建本地映像(NGEN Native Image Generator)
与JIT编译最根本的区别就是,它会整批翻译为针对某特定处理器的本地代码。编译之后我们就可以把程序集以本地代码的方式载入了。
Garbage Collection 垃圾回收
在.NET框架应用程序的执行过程中,托管堆(managed heap)起到了至关重要的作用。每一个引用类型的实例都被分配在堆上,一旦程序运行了,堆内存很快
就会塞满东西,为了创建新的实例,需要更多的可用空间。这个处理过程就是“垃圾回收”。GC回收的是堆内存中的垃圾,这个垃圾其实是一些无用的对象,即不再
被使用的对象。具有较长寿命的对象,会被移至heap尾部,这些对象就成为最后被回收的。一般而言,最新分配的对象,也会是最快成为垃圾的对象,下图的ClassX
就是个不再使用的对象,但是占用了很大的堆内存,回收之后就将其释放掉了。