<<【Perfecssional C# 2008】PartI - 前言 & 背景
这是第1章的内容:
Chapter 1 - .Net Architecture (.Net 构架)
C#和.Net的关系:
C#是一个独立的语言,它并不是.Net的一部分。一些.Net的功能C#并不支持,反过来一些C#的功能.Net也不支持。(例如,operator overloading - 运算符重载) 。但无论怎样,我们想学好C#必须要对.Net有一定程度的认识。
1. The Common Language Runtime (CLR 通用语言运行库)
.Net 构架的重要运行环境叫做CLR或者.Net runtime. 在CLR控制下运行的代码常被称为managed code(托管代码)。
然而,在代码被CLR执行前,你发开的任何代码(C#或其他语言)都需要先被编译过。编译过程在.Net中分为两步:
I. 源代码(Source code) 到 中级语言(Intermediate Language, IL) 的编译
II. IL 到 由CLR分配的专指平台代码 。
这两个编译过程十分重要,因为IL(托管代码)的存在是.Net如鱼得水的基石。
MSIL(微软中级语言)与java byte code(java字节码)有一个共同点:低级(low-level language)并语法简单的语言可以迅速的被编译成机器码(native machine code)。那么拥有一个设计良好的通用语法(universal syntax)就十分重要了:跨平台(Platform Independence),高效率(performance improvement),语言互通(language interoperability)。
跨平台(Platform Independence):
首先,跨平台代表着一个包含相同大小的代码文件可以被放置在任意平台上面;如此一来运行代码时,最终的编译过程将很容易实现,代码便可以在这个特定的平台顺利执行。换句话说,通过将代码编译到中级语言,你就实现了.Net的跨平台过程,跟java所具有的跨平台特性有着异曲同工之意。
可惜,.Net的跨平台还处于理论阶段。在作者写书的时候,微软也只是在windows上将跨平台的.Net完全实现了。
高效率(performance improvement)
从我们前面做过的比较来看,微软的IL要比java byte code更加野心勃勃。 IL是实时编译 (Just-In-Time compiled),而java byte code是(interpreted)解释型的编译。java的一个缺点是,在执行的时候,从java字节码转换到机器码的执行过程中,性能会有所损失。
与一口气将整个应用编译完不同,JIT编译器将代码分成众多份分开编译,所以才叫做实 时编译(Just in Time)。也就是说当代码被编译后,生成的本机exe文件将被储存直到退出应用,因此下次另一部分代码需要被编译的时候,它就不需要被再次编译了。这就 意味着,用不到的功能根本不会被编译到,资源将被合理的利用起来。
语言互通(language interoperability):
这个是说,你能从IL编译成其他语言,同样也能从其他语言编译成IL。所以就互通拉。
COM 和 COM+
从技术角度上说, COM和COM+都不是.NET被关注的技术, 因为基于它们的组件不能被编译成IL。 然而,COM+依然是一个很重要的工具,因为它的功能不与.NET互相重复。而且,COM组件依旧被应用着 - .NET 可以调用COM组件,同样COM组件也可以调用.NET(24章会讲到这些。)
从上一节我们可以看出,IL是.Net构架的基础。作为一个C#程序员,我们至少应该知道在C#代码执行前,它要先被编译成为中级语言IL(当然,C#编译器只能编译成托管代码managed code)。
先来看看IL的一些重要特性:
OO并使用Interfaces(接口)
数字(value)和引用类型(reference type)有着明显的区别
强制数据类型
使用exceptions(例外)来处理错误
使用attributes(属性)
OO并使用Interfaces(接口):
.Net的跨语言在实际应用中有一些限制。IL不可避免的会用到某些普通编程的方式, 也就意味着应用的某个编程方式的同时,这个编程方式也需要与其他的方式相匹配。微软为IL选择和沿用的途径是传统的OO编程,以及单一类的继承(single implementation inheritance of classes)。
除了传统的OO编程之外,IL也用到了接口(interface)的概念, 这便使IL与最初在windows应用的COM分离开了。也就是说.Net下的接口与COM下的接口是不一样的。.Net下的接口不需要支持任何COM的基础构架(比如,它们不是来源于IUnknown, 而且它们不需要与GUI有任何关联, 并且使用了这个接口的类必须提供这个接口特有的方法与属性)。
你现在应该发现,使用.Net就意味着编译成IL,换句话说就是 - 你用的还是传统的OO概念。 但是,单从这方面来说并不足以满足跨语言的条件。毕竟,C++和java都用的是一样的OO规范,可它们还是不能相互协作。
一开始,我们就需要考虑跨语言到底是什么。 说到底,COM在只互相调用方法的情况是允许用不同语言写成的组件在一起工作的。可这有什么不成熟之处呢?COM, 作为一个二进制标准的优点在于它允许组件间的实例化(instantate) ,在不管它是什么语言写成的情况下都可以调用相应的方法与属性。为了达到这个目的,每个object都要先在COM运行环境下初始化,并且通过一个接口来访问。因此,在基于相关组件所用的线程模式之上,分配数据或运行组件或在不同的线程上运行时,都有可能会造成大面积的性能下降。说的极端点就是每一个组件在运行的时候,都像是被放在了一个可执行文件上而不是DLL文件,那么就需要建立不同的进程来运行它们。说来说去,重点还是组件之间可以互相交流,但不单单是通过COM的运行环境。COM不可能发生像由不同语言写成的组件它们之间直接交流或者实例化一个实例(instance) - 因为COM终究只能当做一个媒介来用。不光因为这个,COM构架的本身就不允许使用继承,也就意味着丧失了很多OO编程的优点。
这里还有一些与跨语言相关联的问题, 比如Degbugging(调试)的时候,你还是要分开调试用不同语言写成的组件。因为调试器不允许在语言之间互相切换。因此,我们所说的跨语言实际上是,由一种语言写成的类能够与其他语言写成的类互相交流。一般来说:
一种语言写成的类能够继承其他语言的类
一个类可以包含另一个类的实例,不管它们是什么语言写成的。
一个object(对象)直接调用另一个对象(其他语言写成的)的方法。
对象可以通过方法互相传递。
在调试器里,你可以在调用的方法间互相切换,即使这些方法是用不同语言写的。
这是个很有野心的目标,但是很惊人的是,.Net和IL已经把它实现了。代替了之前在调试器间的方法切换,现在VS这个IDE已经提供了这样的功能。
数字(value)和引用类型(reference type)
IL有个特点就是,数字和引用类型分的十分清楚。数字类型的就是用来直接储存数据的变量。 引用类型的就是用在储存在哪里能找到这个数据的地址。
强制数据类型
IL很重要的一个方面就是十分特殊的强数据类型。 也就是说所有的变量都会被标记成某个指定的数据类型(如果不标记,也就用不到IL了,比如,被VB和脚本语言识别的Variant数据类型。)所以一般来说,IL不允许任何可能导致不清晰数据类型的操作。
比如,VB6的开发者已经习惯了在不用担心数据类型的情况下分配变量,因为VB6会自动执行类型转换。 C++的开发者一般都习惯于指针间的类型转换。使用这样的操作可能会大大提高性能,但会打破类型的安全性。因此,它只允许了在特定的情况下,某些语言中,才能把这种类型转换成托管代码。确实,指针在C#中被放在了标记过程序块时才能够转换成托管代码,而在VB中根本不能实现。
你应该已经注意到了,一些与.Net兼容的语言,比如VB2008,依然是允许一些类型的不规范,但这是只是因为幕后的编译器已经确认了那个类型在编程IL时是安全的。
尽管强制类型安全一开始会妨碍到总体性能,但总的来看大部分.Net的提供的服务都会因为强制类型而受益匪浅,从这个角度上看它的重要性远远大于这一小点的性能损失。这些服务包括:
语言协作性
无用单元收集
安全
应用域
接下来我们看看,强制类型到底在这些服务中起到的哪些作用:
语言协作性:
假如一个类包含或得到了另一个类的实例,它就需要另一个类所使用的所有数据类型。这就是为什么强制数据类型重要的原因了。
通用类型系统:
这种类型的问题在.Net中通过使用Common Type System(CTS通用类型系统)得到了解决 。CTS定义了那些IL中存在的预先定义数据类型,所以所有以.Net构架作为目标的语言最终都会生成基于CTS的编译代码。
以之前的例子为例,VS2008的Integer(整数)事实上是32-bit(32位)的带符号整数(Signed integer),也就是IL类型中的Int32。那么我们就可以把它看成IL中的数据类型。因为C#的编译器可以识别这种类型,所以不会有任何问题。从代码的角度来看,C#中Int32用keyword(关键字) int表示,所以编译器会自动把VS2008的方法的返回值当做int。
CTS不会明确的表示primitive data types(基础数据类型),但它有丰富的类型阶层(hierarchy of type),也就是说这个类型阶层中特定的点,在代码中被允许定义它自身类型。CTS的结构构架很好的符合了IL的single-inheritance (单一继承)的OO模式。
类型 | 意义 |
Type | 基础类,表示任意类型 |
Value Type | 基础类,表示任意数字类型 |
Reference Types | 任意通过reference(引用)来访问数据并且把数据储存在heap(堆阵)上的数据类型 |
Build-in value type | 包含了大部分用来表示数字,布尔值或字符的基础类型 |
enumerations | 一套枚举类型的值 |
User-defined value types | 在源代码中被定义并且储存成值类的类型。在C#中,就是struct(结构类型) |
Interface types | 接口 |
pointer types | 指针 |
self-describing types | 为了方便垃圾回收器而提供自身信息的数据类型 |
arrays | 包含了一个object阵列的任意类型 |
class type | 自我叙述但不是阵列的数据类型 |
delegates | 用来保存访问方法的引用的类型 |
user-defined reference types | 在源代码中定义并且作为引用类型保存的类型。在C#中,这代表了任意类 |
boxed value types | 一个值类型,被暂时保存在一个引用类型中,并且储存在堆阵中。 |
在后面第3章会有详细分析。 此处不做详解。
Common Language Specification(通用语言规范):
CLS与CTS之间协同工作来保障语言互相协作。CLS是一组支持.Net的小规范的集合。因为IL是个很丰富的语言,所以众多编译器写手更喜欢把自己限制在某个规范内来编写。只要他们写出的编译器支持CLS所规定的所有东西,这都不是问题。
当然我们也可以编写不支持CLS规范的代码,但这样的话这些代码是否可以被成功的编译成IL代码,那就不能保证了。
比如,大小写问题。IL是需要区别大小写的。那么试用了区别大小写语言的程序员在定义变量名称时会得到很多好处。可是,VB2008不区分大小写。CLS不希望两个仅从大小写上就把两个相同的名字的变量区分开来,因为这样在将来会带来更多麻烦。因此,VB2008只能识别符合CLS规范的代码。
这个例子可以说明CLS的两个工作方式:首先,它意味着单独的编译器不需要强大到包含所有的.Net框架的功能。其次,它为跨语言替工了保证,只要你的代码是按照CLS的标准编写的,那么其他语言写的编译器一样可以识别你的代码。
这个方法的奇妙之处再出,按照CLS标准来编写的代码只作用于public和protected类的成员。也就是说在一个private私有的应用中,你可以随便写非CLS标准的代码,因为在其他程序集不可能访问到你这部分代码。也就不用担心标准问题了。
在这里不会做CLS的进一步解释,因为他对你C#编程的影响并不太多,而且C#中也包含了一些非CLS保准的功能。
Garbage Collection (无用单元收集)
Garbage collector (无用单元收集器)是.Net针对内存管理而出台的方案。到目前为止windows平台上只有两种方案是用来重新分配(de-allocating)进程从系统中动态获取的内存的:
让应用应用本身的代码来手动完成所有操作。
做一个包含reference counts(参考读数)的objcet。
用应用本身的代码来完成重新分配内存是个十分低级的技术,但同时也是十分高效的,比如C++。这种方法效率很高,它的好处在于资源从来不用存在过长时间(比如,比它本身所需求的时间还要长)。但有一个很大的缺点是,经常出现bug。调用内存的程序自己要分别告诉系统,它不再需要那些内存了。但这样做容易被遗漏,从而引起内存泄露(memory leak)。
尽管现在的程序都提供了帮助侦测内存泄露的工具,可它们还是会留下很多难发现的bug。 因为它们只有当已经存在很多内存漏洞了,并且windows拒绝服务更多的进程了,才会起到作用。所以,这是计算机就会变得很慢很慢,因为它已经没有更多的内存来提供给这些程序了。
COM喜欢的保留一个引参考读书来做无用内存回收(方法2)。这个主意是使每一个COm的组件都维持一个计数器,记载着有多少个用户正在使用这个组件,换句话说就是有多少个(reference)引用存在。当这个技术到0时(就是说没人用它了),组件就自我销毁并且释放内存。但是问题是,这依旧需要用户来告诉它它们已经使用完这个组件了。
.Net运行环境依赖于无用内存回收器(方法1)。这个程序的主要目的是用来清理内存的。动态获取的内存被储存在堆阵中(在所有语言中都是如此,尽管.Net中CLR拥有它自己的managed heap(托管堆))。有时,当.Net发现某个城区的托管堆快满了并且需要清理的时候,它就调用内存回收器。然后回收器通过查看你所有代码中变量,并检测储存在堆阵中object的引用,来确定哪个是你程序所调用的 - object所引用的那个。其他任何没有被引用的object都被移除掉了。Java的无用内存回收系统跟这个类似。
无用内存回收能在.Net中工作,因为IL在设计的时候就此做过优化。 无用内存回收同样也能在C++中实现,因为C++允许指针自由的在不同类型中强制转换。
无用内存回收很重要的一个方面是,它的不可确定性。换句话说,就是你不能保证什么时候无用内存回收器会被调用。它只在CLR觉得需要它的时候才会被调用。当然,你也可以通过代码来调用它。
安全:
.Net提供的是code-based security(代码为基础的安全机制),而windows提供的是role-based security(角色为基础的安装机制)。
角色为基础的安全机制是基于运行某个进程的账户身份 。代码为基础的安全机制则是要明白这些代码的用作以及哪些代码是安全地。这要多亏了IL的存在,使得CLR可以在代码执行前检测它的安全权限。.Net也提供了一种机制可以给特定的代码添加权限,指定它需要什么样的安全权限来运行。
代码为基础的安全机制可以减少由可疑代码运行所带来的安全隐患。比如,即使代码是在admin用户下运行的,代码本身也可能存在安全问题。那么如果只是角色为基础的安全机制就无法检测到这些安全隐患。
应用域:
应用域是.Net的重要创新之一,它大大减少了既需要分开运行,又需要互相交流的程序间执行时的消耗。经典的案例是Web Server Application(网络服务器应用) - 众多应用需要同时为用户提供服务。
在.Net出现之前,如果想达到这样的效果要么把这些应用分开在不同的进程中运行(总体性能会大幅度下降),要么这些应用间共享一个进程(这么做可能会因为一个应用的崩溃而导致整个系统的瘫痪)。
到目前为止,唯一孤立代码的手段是通过进程来实现。当你开始一个新的应用时,它只在一个进程的环境中运行。 Windows在地址空间中把进程与进程间隔离开来。这根据的是每个进程都拥有4G的虚拟内存用来储存它的数据以及执行代码(4G是32位的系统,64位的会提供更多虚拟内存)。Windows强制把这段虚拟内存分配到某个特定的物理内存或硬盘空间的区域来建立一个间接级别。每个进程都会得到一个不同的分配,并且不会在虚拟地址块被分配到同样的物理内存上。
一般来说,任意进程都能够通过解析虚拟内存地址来访问物理内存 - 进程不能够直接访问物理内存。 因此,一个进程访问另一个进程上的所分配内存,就变的不可能了。这就保障了恶意的代码不会伤害到它地址空间以外的任何东西。
进程不单单是用来隔离程序间互相运行代码的。 在Windows XP/03/Vista中,他们也是用来整理安全和权限的分配的。每个进程都有他自己的安全标记,用来给windows指出它哪些操作是被允许的。
尽管从安全角度来看,进程很好用。可是它们在性能方面有一个很大的缺陷。 通常,几个进程需要一起工作,并互相交流。一个很显而易见的例子是一个进程调用COM组件,这个组件是可执行文件,就需要它自己的单独的进程来支持。如果调用的COM的代理项(surrogate)那么也会出现同样的情况。因为进程之间不能共享内存,一个复杂的调度(marshalling)进程不得不在其间互相复制数据来完成交流。这对整体性能是个很沉重的打击,如果你不想整体性能有损,而又需要互相交流,那么就需要用基于的DLL组件,而且所有东西都要在一个地址空间上运行 - 这带来的危险就是如果一个出错那就会连累所有东西崩溃。
应用域就是被设计成用来分散组件的方法,进而达到各个进程间能够互传输数据而不造成性能损失。应用域的关键是一个进程被分配到了多个应用域中。每个应用域对应一个应用,每个执行的thread(线程)都会在它专属的那个应用域运行。
假如一些不同的可执行文件在同一个进程空间中运行,那么它们之间就很容易共享数据,因为,理论上说,它们可以直接看到对方的数据。尽管,从原理上讲这是可能的,但CLR使得它不能够在实际操作中实现。因为,CLR要检查并确定代码在它自己的数据范围内运行。
Use of Attributes(属性的使用)
.Net中用用一种语言定义的属性可以被其他语言读取。Chap13 中详解。
程序集是.Net中包含了以编译代码的逻辑单位。Chap17中详解。
一个程序集是完全自我解释并且更趋向于逻辑层而不是物理层的单位,也就是说它可以被多个文件访问(动态程序集储存在内存中,而不是存在某个文件上)。假如一个程序集被储存在了多个文件中,其中便有一个主文件用来储存entry point(入口点)和其他文件的描述。
要注意的是可执行代码与库代码使用的是相同的程序集结构。唯一不同点是可执行代码的程序集包含了一个主程序的入口点,而库代码没有。
程序集的一个重要特征是,它包含了用来解释相应代码类型与方法的metadata(元数据 - 用于描述数据的数据)。同样也包含了用来解释它自身程序集的元数据。程序集元数据被存在叫做manifest(清单)的区域,允许用来查看程序集的版本和它本身的integrity(储存程序)。
事实上,程序集内部包含了元数据后,当其他程序或者程序集调用这个程序集时就不需要访问注册表或者其他的数据源来找出如何使用这个程序集。 这便是与老式的COM相比的不同之处了,组件的GUID和接口都需要从注册表里获得,并且有时候,方法或者特性的详情需要从类库中获得。
程序集都两种:private(私有) 和shared(共享)程序集
- 私有程序集:
私有程序集是最简单的类型。它们一般都和其他软件附在一起并且只供此软件使用。 最常见的私有程序集的例子是, 一个可执行程序和一些库,这些库只能由这个程序本身是用。
系统会保证私有程序集不能够被其他的软件所使用,因为一个应用只会加它可执行文件目录,或者自目录下的私有程序集。
因为你一把安装商业软件的时候,所有的文件都是被安装在同一个目录下面的,这样就会避免软件之间的覆盖,改写,或者加载错误的程序集。并且,私有程序集只能够被软件本身所访问。
本为私有程序集是完全自足的, 所以部署的步奏就很简单。你只用把相应的文件放到相应的文件夹中就可以了。无需注册。这个过程叫做zero impact(xcopy) installation(无影响安装)。
- 共享程序集:
共享程序集是用来与其它应用共同使用的共有库。因为任意软件可以访问共享程序集,因此更要留心以下几处地方:
名字重复,也就说其他公司的共享程序集与你公司的共享程序集名字一样。因为客户端代码会同时访问这两个程序集,进而可能造成很严重的问题。
一个程序集可能被不同版本的同一个程序集覆盖 - 这样新的版本可能会与现有的客户代码不兼容。
解决这些问题的办法是把共享的程序集放在一个叫GAC(global assembly cache 全球程序集缓存)的地方。不像私有程序集那样,这个方法不会只是拷贝到这个文件夹中那么简单 - 它需要被分别安装到缓存当中。这个步奏可以通过.Net的一些实用工具来实现,并且需要检测这些程序集,同样在这些程序集缓存中建立一个小的文件夹层级来确保它们的完整性。
为放置名字冲突,共享程序集的名字是由私有匙加密技术得出的,这个名字也叫做strong name(强式名称);它确保了共享程序集的唯一性。
覆盖的问题可以通过在程序集清单上注明版本以及并行安装来解决。
.Net基础的类是一个允许你实现所有windows API中提供的任务的巨大托管代码集合。 这些类所用的对象模型与IL是相同的,都基于单一继承。也就说你既可以创建自己的类,也可以使用.Net类库中所提供的类。.Net 3.5的基础类都包含了以下地方:
核心特点是由IL提供的
Windows GUI 的支持与控制
Web Forms 窗体(ASP.NET)
Data access 数据访问 (ADO.NET)
Directory access 路径访问
File system and registry access 文件系统以及注册表
网络以及网页浏览
.Net 属性以及reflection 镜像
Access to aspects of the windows OS
COM 互用性
命名空间是.Net用来避免类名称冲突的一个方法。命名空间也不过是一组数据类型而已,但是在一个命名空间中的所有数据类型,都被加上了这个命名空间的名字作为前缀。命名空间之间也可以互相镶嵌。比如,.Net的基础类都在一个叫做System的命名空间中,那么它包含的基础类Array在这个命名空间中就是System.Array
.Net要求所有的类型都需要在一个命名空间中;比如,一个叫Customer的类,你可以把它放到叫YourCompanyName的命名空间中。那么这个类的全名就是YourCompanyName.Customer
假如没有明确指出命名空间的话,那么这个类型将被添加到nameless global namespace(匿名全局命名空间)
一般微软都建议你使用两层命名空间,比如第一层是你的CompanyName,第二层是你的项目名称SaleService最后一层才是你的具体事项Customer,那么访问起来就是CompanyName.SaleService.Customer。 这样不但清晰易懂,而且不容易有遗漏。
5. Creating .Net Apps Using C# (C#应用)
ASP.NET的特性:
首先,也可能是最重要的,ASP.NET的pages(页面)都是structured有结构的。 每个页面都是从System.Web.UI.Page 这个类继承下来的并且废除了一些在Page object的生命周期中运行的方法。
ASP.Net允许你将一个页面在服务器端的功能独立到一个类中,然后把这个类编译成DLL,并把这个DLL放到HTML部分的目录下面。用code-behind(后台代码)的指令(directive)在这个页面的最上面把DLL文件连接起来。当浏览器请求这个页面时就会触发这个DLL文件。
在整体性能上面,ASP.NET也有非凡的提升。它将第一次请求的页面储存在缓存当中,然后借来下对于同一个页面的请求速度就比第一次快出一大截了。
Windows Presentation Foundation (WPF)
WPF用XAML来编写应用。XAML是Extensible Application Markup Language。
WPF是declarative programming(声明式编程)的重要一步。 声明式编程就是代替了原先通过高级语言C#,JAVA,VB来创建object的方式,而直接通过XML声明一切东西的编程方式。
windows 服务
windows服务也叫NT服务,是被设计成在Win NT/2k/Xp/Vista(not 9x)后台运行的程序。这些服务在你想要一个连续运行并且能够随时响应事件的程序,而你又不想分开执行那么多次的时候就十分有用。 WWW服务就是一个很好的例子,它随时监听着从客户端来的网络请求。
在C#中编写一个服务并不难,只需要用System.ServiceProcess命名空间就足够了。
6. C# in .Net Enterprise Architecture (企业构架)
使用ADO.NET, C#可以快速从SQL服务器或者Oracle数据库中获取数据。返回的datasets可以通过ADO.NET面向对象模型或LINQ来操作,然后自动选染成XML用于网络传输。
做为第一个基于组件的C语言,C#也可以用来植入商业对象层。它加速了组件内部交流的研究,把开发者们的重点集中到了DAO(数据访问对象)上面,强制加速了他们的商业定律。再者,有了attributes(属性),C#的商业对象就能满足在method-level(方法层)的安全检测,object pooling(对象池化),还有由COM+服务提供的JIT的激活。
用C#来建立一个企业级的应用,你要为DAO和其他的商业对象建一个类库。在开发过程汇总,你可以通过控制台项目来测试类的方法。对于极限编程的爱好者们,可以建立能够直接从batch file(批处理文件)执行的控制台项目,以用来测试。
C#也会影响到你打包重复使用类的过程。在过去,一般都把这些类放在一个物理组件当中,因为这样更容易部署;但在.NET企业应用中,你可以把这些类放在分散的组件当中,以更符合逻辑的方式来分布它们,省去了繁琐的DLL群。
最后就是当你编辑web form的时候会有更多的便利。不详细说了,就是很吊了~
这章包括了很多基础、概括的描述了.NET重要的一些特性。并且也对.NET与C#的关系做了概要描述。文中提到了很重要的一点就是所有针对于.NET的语言都会在被CLR执行前,先被IL编译成托管代码。同样也提到了关于编译与执行过程的一些要素:
程序集和.NET基础类
COM组件
JIT编译
应用域
无用内存回收
图1-4提供了这些特性的总览:
从这一章,我们学到了IL的特性,特别是它的强制数据类型和面向对象,并且学到了这些特性是如何影响到.NET框架的。我们同时也学到了IL的强制数据类型是如何促进语言间的沟通的,还学了一些CLR服务。比如无用内存回收和安全。不光这些,我们还了解了通用类型系统和通用语言规范是如何帮助语言互动的。
最后,我们还了解了C#如何应用在.NET框架上。
在下一章里,我们将要学习如何用C#来编程。
期待啊~!
联系客服