Thursday, December 21, 2006

(ZT)CTS

通用类型系统(Common Type System,以下简称CTS,有的地方也叫公共类型系统),算是个老话题了,但对于部分.NET程序员来说,可能还是需要这方面的知识。加上CTS是.NET里各种语言的基础,为了更好的进一步学习各种语言特性以及.NET Framework,也不得不了解CTS。我近期也在准备着一些.NET入门到提高学习资料,所以顺便整理下相关资料贴出来供大家参考(有可能的话,会按我整理的从入门到提高的系列一篇篇贴出来,不过就是要等待些时间),哪怕你对其中一点有心领神会的感觉,那也就够了。OK,转入正题。

一、CTS简介

我们都知道,编程语言发展至今已经有几百种,面对这么多的编程语言,经常有人陷入类似“生死抉择”的痛苦,但是有点体会的程序员总会说:“语言是互通的,只要你掌握了编程思想,遵循相应编程规范,就不用怕今天又出现了多少新语言”。我想这句话确实是有道理的。在这众多语言的背后,也隐藏着某种共性,不仅在语言本身,也在使用语言的方法上。就象大家常理解“程序=算法+数据结构”一样,我们也可以这样理解语言的共性,即“语言=语法+语义”。单纯字面上理解(不涉及编译等具体技术),语言实际上就是用各种事先定义好的关键字以一种特定组合方式起来的表现形式,用以定义数据以及描述对数据的操作。
了解了语言的概念,让我们思考下,是否存在一种东西(请原谅我用“东西”这个词)满足所有语言的共性?假如众多语言有了一种共性,是否可以进一步让众多语言之间实现互操作?再者,结合面向对象里的单根对象层次观点让每个对象最终都有相同的基类型,是否就又有了类型的统一功能?接着在统一类型基础上,又有了统一的调试方式、统一的体系框架、统一的开发手段等等呢?好了,再说下去都成完美神话了。上面的“东西”,实际上就是CTS。也正因为有了CTS,才有了.NET里“统一”的诸多特性,把CTS说成CLR(Common Language Runtime 通用语言运行库)的核心,编程人员必须掌握的特性一点也不为过。
下面引用MSDN里的CTS定义让大家有更清楚了解CTS,毕竟MSDN是权威:

通用类型系统定义了如何在运行库中声明、使用和管理类型,同时也是运行库支持跨语言集成的一个重要组成部分。通用类型系统执行以下功能:

  • 建立一个支持跨语言集成、类型安全和高性能代码执行的框架。
  • 提供一个支持完整实现多种编程语言的面向对象的模型。
  • 定义各语言必须遵守的规则,有助于确保用不同语言编写的对象能够交互作用。

清楚了CTS的概念与优点后,我们更进一步来了解CTS。按照上面所讲的语言与CTS的关系,我们可以把CTS看成是.NET是所有语言的超集,也就是说各种符合CTS的语言,实际上都是CTS的一个子集。用数学的集合观点理解就是如下图:

从上图还可以看出在各种语言之间还有个交集——通用语言规范(Common Language Specification,简称CLS),这里也简单说下。我理解中,CTS是一套类型系统,而CLS则是事先定义好的一种规则,遵循这种规则编写的任何代码都可以实现跨语言的互操作性。事实上,通用语言基础结构(Common Language Infrastructure, 简称CLI,是CLR的子集。CLR与CLI的关系类似CTS与CLS的关系。)正是使用CLS来让各种不同的语言使用.Net框架各种资源。在实际应用上,对于CLS,如果我们项目里都只用一种语言,例如C#,那就没必要过度关心CLS。相对地,如果是多种语言结合开发,或者希望你开发的类库能让各种不同语言调用时,那么CLS绝对是你需要进一步了解的内容。

 

二、了解CTS前的准备

1、 首先,说说堆(heap)与栈(stack,也叫堆栈),理解了最起码的堆栈概念,对于理解CTS绝对是必要的,而在最近给公司新员工培训时,发现他们根本不了解这块,所以我也觉得有必要单独再稍微说明下。对于heap和stack,简单的来讲,stack上分配的内存系统自动释放,heap上分配的内存,系统不释放,哪怕程序退出,那一块内存还是在那里。stack一般是静态分配内存,而heap上一般是动态分配内存。也正因为它们这种特性,在.NET里,对于托管堆(managed heap)采取了通过垃圾回收器(Garbage Collection,简称GC)进行自动回收内存操作。下面是stack和heap的对比说明,可以让你更好理解它们:

l Stack:堆栈本身就具有回收特性,每当程序执行到区块的结束点时,该区块的内存就自动被释放(Last-In-First-Out,简称LIFO,后进先出),这让我们没必要担心程序里使用了多少变量,因为变量在区块外立即自动被释放,保证了一直有足够内存空间。堆栈虽好,但可惜的是并不是所有信息都可以存储在堆栈中。例如我们经常使用的string类型变量,之所以把string变量存储在Heap中,是因为字符串的长度是可变的,无法直接在堆栈中分配内存空间,因为堆栈是静态分配内存的。

l Heap:堆上的变量可以在任何地方被创建,在.NET里托管堆通过GC进行回收,至于回收方式,GC是通过标志方式进行回收的,即若有堆中的变量被标志为“待回收”,则未来GC会释放该内存空间。当然GC的回收远远不是这么简单,这里略过(有兴趣可以查看SDK文档或《深入理解.NET内存回收机制》一文)。

2、接着看下C#预定义类型:

运行时类型

C#预定义类型

描述

System.SByte

sbyte

8位有符号整数

System.Byte

byte

8位无符号整数

System.Int16

short

16位有符号整数

System.UInt16

ushort

16位无符号整数

System.Int32

int

32位有符号整数

System.UInt32

uint

32位无符号整数

System.Int64

long

64位有符号整数

System.UInt64

ulong

64位无符号整数

System.Char

char

一个16位Unicode字符

System.Single

float

单精度32位浮点数

System.Double

double

双精度64位浮点数

System.Boolean

bool

布尔值 true/false

System.Decimal

decimal

128位数据类型

System.Object

object

根类

System.String

string

Unicode字符串

在.NET里,所有类型都是对象,而且都直接或间接继承自System.Object类。因此,例如我们声明并创建一个 int 类型时,实际创建了System.Int32的一个实例。例如:

1

public int i = 999;

2

public int j;

上面第1行代码,表示声明了一个 int 类型的变量 i,并赋值999。其实际是创建了 System.Int32 的一个实例,在堆栈Stack(为什么不是在heap堆上创建呢?这个后面会讲到是因为值类型和引用类型的区别)上分配一个内存空间存储了值999。

而第2行代码,只是声明了j,而没有进行赋值操作。在C#里,跟C++等不同的是,当你仅仅进行声明变量时,所发生的事就仅仅是“声明”,而没有进行实例的创建,也就没有进行内存空间的分配。例如,你声明了如下语句:

1

System.Web.UI.Page page;

上面仅仅就是个声明而已,没有真正创建page实例,也就是说没有在内存空间里实际进行内存的分配。而你要进行创建实例,并进行内存分配,有且仅只有通过

1

System.Web.UI.Page page = new System.Web.UI.Page();

进行创建。也正因为这样,我们可以通过ILDASM看到上面声明的 j 实际是没有进行内存分配存储的。这也算是C#的一个新特征。这里额外也说了下。

下面是三个概念的理解:

l Managed Heap: 托管堆。是由CLR动态分配的连续的内存空间,在执行阶段由GC负责管理。整个进程共用一个托管堆。

l Call Stack: 这是由CLR在执行时自动管理的内存空间,每个线程都有自己的Call Stack。每调用一次方法,Call Stack就会多一次记录,调用完毕该记录随即被丢弃。

l Evaluation Stack:这是由CLR在执行时自动管理的内存空间,每个线程都有自己的Evaluation Stack。事实上,我们在CTS里常说的线程堆栈指的就是它。

0 Comments:

Post a Comment

<< Home