.NET体系结构
.NET体系由公共语言运行库(CLR)(Common Language Runtime)和.NET Framework类库所构成。CLR是.NET Framework 的核心执行环境,也称为.NET运行库。.NET Framework类库是一个内容丰富的类集合,它可以完成以前要通过Windows API来完成的绝大多数任务。
一、CLR概述
2. 提高性能。首先,IL比Java的字节码作用还要大,因为IL是即时编译的,而Java的字节码常常是解释性的,在转换为平台可执行代码的时候可能会导致性能损失。其次,.NET的即时编译器(JIT)(Just In Time)并非一次把全部代码编译完才执行,而是只编译调用的那部分代码,并把得到的这部分内部可执行代码保存起来,下次需要调用的时候无须重新编译。Microsoft认为这个过程要比一开始编译整个应用程序代码的效率高得多,因为任何程序的大部分代码实际上并不是在每次运行过程中都执行。最后,传统的编译器会优化代码,但它们的优化过程是独立于代码所运行的特定处理器的。例如Visual Studio 6优化了一台一般的Pentium机器,它所生成的代码就不能利用Pentium Ш处理器的硬件特性。而JIT与平台无关,所以它可以针对不同的机器完成不同的优化。
3. 语言的互操作性。互操作性即能将任何一种语言编译为中间代码,编译好的代码可以与从其它语言编译过来的代码进行交互操作。在.NET中可以交互操作的语言有C#、VB.NET、Visual C++.NET、Visual J#、脚本语言、COM和COM+。
二、中间语言
中间语言(IL)在.NET Framework中有非常重要的作用,所有面向.NET的语言都要先编译成IL,那么它们在逻辑上都需要支持IL的主要特征。IL的主要特征:
1.面向对象和使用接口
2.值类型和引用类型之间的巨大差别
3.强数据类型
4.使用异常来处理错误
5.使用特性(attribute)
下面详细讨论一下这些特性。
1. 面向对象和接口的支持
Microsoft为IL选择的特定道路是传统的面向对象的编程,带有类的单一继承性。此外IL还引进了接口的概念。.NET接口与COM接口不同,它们不需要支持任何COM基础结构,例如,它们不是派生自IUnknown,也没有GUID。但是它们与COM接口共享下述理念:提供一个契约,实现给定接口的类必须提供该接口指定的方法和属性的实现方式。
2. 值类型与引用类型
与其它编程语言一样,IL提供了许多预定义的基本数据类型,IL的一个特征就是值类型和引用类型有明显的区别。对于值类型,变量直接保存其数据,在堆栈上面分配存储空间;而对于引用类型,变量保存数据的地址,引用类型的数据在堆上分配存储空间。
3. 强数据类型
IL的一个重要方面是它基于强数据类型,即所有的变量都清晰地标记为属于某个特定数据类型,IL一般不允许对模糊的数据类型执行任何操作。
(1)语言的互操作性
(2)垃圾收集
(3)安全性
(4)应用程序域
下面我们分别阐述一下:
如果类派生其它类,或包含其它类的实例,它就需要知道其它类使用的所有数据类型,这就是强数据类型非常重要的原因。假定VB.NET类中的一个方法被定义为返回一个整型,但C#中没有该名称的数据类型,如果要实现语言的互操作性,那么编译器就需要知道如何把VB.NET的整型类型映射为C#定义的某种已知类型,这样就可以在C#代码中使用返回的类型。
这个问题在.NET中是通过通用类型系统(CTS)(Common Type System)和公共语言规范(CLS)(Common Language Specification)来解决的。
首先CTS定义了可以在中间语言中使用的预定义数据类型,所有用于.NET的语言都可以生成最终基于这些类型的编译代码。例如,VB.NET的整型实际上是一个32位的有符号整数,它映射为IL的类型Int32,C#中的int也映射为IL的类型Int32,这样C#代码和VB.NET中的整型数据就可以通过IL类型Int32建立相应的关系,从而实现互操作。
其次CLS和CTS一起确保语言的互操作性。CLS是一个最低标准集,所有面向.NET的编译器都必须支持它。
垃圾收集器用来在.NET中进行内存管理,特别是它可以恢复正在运行中的应用程序需要的内存。到目前为止,Windows平台已经使用了两种技术来释放进程向系统动态请求的内存:(1)完全手工方式使应用程序代码完成这些工作。(2)让对象维护引用计数。
第一种方式是低级、高性能的语言使用技术,例如C++。这种技术很有效,能即时释放资源,但是最大的缺点是容易出错,遗忘释放内存,导致内存泄漏。尽管现代开发环境提供了帮助检测内存泄漏的工具,但它们很难跟踪错误。
第二种方式是COM对象采用的一种技术,其方法是每个COM组件都保留一个计数,记录客户机目前对它的引用数。当这个计数下降到0时,组件就会删除自己,并释放相应的内存和资源。但它仍然需要客户机通知组件它们已经完成了内存的使用,只要有一个客户机没有这么做,对象就仍驻留在内存中。在某些方面,这个比C++内存泄漏更为严重,因为COM对象可能存在于它自己的进程中,从来不会被系统删除。
.NET采用的方法是垃圾收集器,这是一个程序,其目的是清理内存,方法是所有动态请求的内存都分配到堆上,当.NET检测到给定进程的托管堆已满,需要清理时,就调用垃圾收集器。垃圾收集器处理目前代码中的所有变量,检查对存储在托管堆上的对象的引用,确定哪些对象有引用,哪些没有引用,没有引用的对象就不能再从代码中访问,即可以被删除。Java就使用与此类似的垃圾收集器系统。
应用程序域是.NET中的一个重要技术改进,它用于减少运行应用程序的系统开销,这些应用程序需要与其它程序分离开来,但同时还需要彼此通信。典型的例子是web服务器应用程序,它需要同时响应许多浏览器请求,因此,要有许多组件实例同时响应这些同时运行的请求。
到现在为止,孤立代码的唯一方式是通过进程来实现的。在运行一个新的应用程序时,它会在一个进程环境内运行。Windows通过地址空间把进程分隔开来。这样,每个进程有4GB的虚拟内存来存储其数据和可执行代码(4GB对应32位系统),虚拟内存映射到物理内存,物理内存不能有重叠,这种情况如下图所示:
进程对确保安全有很大的帮助,而它们却存在性能的缺点。往往多个进程需要一起工作,且需要相互通信。例如进程调用一个COM组件,而该COM组件是可执行的,需要在它自己的进程上运行。因为进程不能共享任何内存,所以必须使用一个复杂的编组过程在进程之间复制数据。这对性能有非常大的影响。避免性能影响的唯一方法是基于DLL的组件,让所有的组件在同一地址空间运行——其相关的风险是执行出错的组件会影响其它组件。
应用程序域是分离组件的一种方式,它不会导致因在进程之间传送数据而产生性能问题。其方法是把任何一个进程分解到多个应用程序域中,每个应用程序域大致对应一个应用程序,执行的每个线程都运行在一个具体的应用程序域中,如下图所示:
三、程序集
程序集(assembly)是包含编译好的、面向.NET Framework的代码的逻辑单元。它是完全自我描述性的,是一个逻辑单元,可以存储在多个文件中。如果一个程序集存储在多个文件中,其中就会有一个包含入口点的主文件,该文件描述了程序集中的其它文件。
程序集包含程序的元数据,元数据描述了对应代码中定义的类型和方法以及描述程序集本身,这样其它程序在调用该程序集的时候,不需要指定注册表或其它数据源。通过元数据还可以将程序集放在不同的位置上而不影响数据的同步问题。
程序集有两种类型:共享程序集和私有程序集。私有程序集一般附带在某些软件上,且只能用于该软件中。系统可以保证私有程序集不被其它软件使用,因为应用程序只能加载位于主执行文件所在文件夹或其子文件夹中的程序集。共享程序集是其它应用程序可以使用的公共库。
四、.NET Framework类
.NET Framework类是一个内容丰富的托管代码类集合,它可以完成以前要通过Windows API来完成的绝大多数任务。这些类派生于与中间语言相同的对象模型,也基于单一继承性。可以实例化.NET Framework类,也可以从它们派生自己的类。.NET Framework类直观易用,它结合了Visual Basic和Java库的易用性和Windows API函数的丰富功能。.NET Framework类包括:
1.IL提供的核心功能,例如,通用类型系统中的基本数据类型
2.Windows GUI支持和控件
3.Web窗体
4.数据访问
5.目录访问
7.网络和web浏览
8..NET特性和反射
9.访问Windows操作系统的各个方面(如环境变量等)
10.COM互操作性