1.3.2 值类型和引用类型
和其他的任何一种编程语言一样,IL提供了许多预先定义好的原始数据类型。但是,明显区别值类型和应用类型也是IL的一个重要特性。值类型就是那些直接存储数据值的那些类型,而引用类型只是存储数据值所在的内存单元的地址,通过这个地址,我们就可以找到相应的数据。
在C++的术语中,与引用类型相似的是指针,而在Visual Basic中,对象与这里的引用类型最相似,在Visual Basic中对象总是通过引用来访问的。IL同样因为存储引用类型作了详细的规范,它们总是被存储在一块叫做托管堆的内存里,相反值类型的变量通常是存储在堆栈中的(尽管在一个引用类型中定义了一个值类型,但它们仍然被内联存储在堆中)。我们将要在第三章讨论堆栈和堆,以及他们是怎么工作的。
1.3.3 强数据类型
IL的一个重要特性是它是基于强数据类型异常检查的。这就是说在IL中所有的变量都必须被清楚的表明变量特定的数据类型(例如,像在Visual Basic 和 脚本语言中允许的Variant数据类型在IL是不存在的)。特别的,IL不允许引起歧义性数据产生的操作。
举个例子来说,因为Visual Basic 6可以自动的进行类型转换,所以Visual Basic 6开发者以前可以传递变量而不用过多的考虑他们的类型。C++开发者以前习惯于在不同类型之间转换指针的类型。执行这样的操作可以极大地提高性能,但是这样也破坏了类型的安全性。因此,这种情况仅仅是在某些需要编译为托管代码的语言的特定情况下才被允许。确实是这样,指针(相对于引用)只在被标记了的C#代码块中才被允许,而在Visual Basic中根本不能使用指针(尽管在托管C++中允许使用)。在代码中使用指针会导致在CLR执行的类型安全性检查中失败。
你应该注意到仍然有一些.NET依然允许在类型检查上面放松要求,比如Visual Basic .NET,但也只是编译器确定这样的类型能够保证后台IL的类型安全性。
尽管强制类型安全性检查也许会带来性能的损失,但是在许多情况下,.NET所提供的各种服务所带来的好处远远超过了类型安全性检查所带来的性能损失。这些服务包括:
❑ 语言互操作性
❑ 垃圾收集器
❑ 安全性
❑ 应用程序域
下面就让我们赖以其具体了解一下为什么强数据类型对.NET的这些特性特别重要。
1.3.3.1 对语言互操作性来讲强数据类型的重要性
如果一个类是有其他的类派生而来,或者是想要包含其他的类的实例,他就需要知道其他的类所用到的数据类型。这样正是为什么强数据类型这么重要的原因。确实,过去正是没有这数据类型信息方面的详细规范才总是阻碍语言之间相互继承以及实现互操作性。这类信息不是简单的出现在标准的可执行文件或者DLL文件里。
假设一个Visual Basic .NET的类的一个方法定义了返回值是一个“整型“——Visual Basic .NET的一种标准数据类型。但是C#却没有这样名称的一个数据类型。显然,如果编译器知道如何将Visual Basic .NET的整型映射为C#已经定义的某种类型的话,我们就可以在Visual Basic .NET获得从C#的类里继承,调用方法,使用返回值的能力。
通用类型体系
这个数据类型方面的问题.NET是通过Common Type System (通用类型体系,简称CTS)解决的。CTS定义了一些在IL中可以使用的预先定义数据类型,这样所有希望以.NET Framework为运行平台的语言最终都需要生成建立在这些类型上面的代码。
在考虑一下我们刚刚举的那个例子,Visual Basic .NET的整型数据类型实际上是一种32位带符号整数,这种数据类型与IL中的数据类型Int32完全吻合。因此这就是IL代码的数据类型明细。因为C#也知道这种数据类型,所以现在前面的问题已经解决了。在源代码中,C#与Int32所对应的关键字是int,所以只需要简单的做一下转换,将Visual Basic .NET方法的返回类型转换为int。
CTS不仅仅是定义了基本数据类型,他还定义了一个内容丰富的类型层次结构,这种层次结构包含了定义好的要点,其他的语言可以在此基础上定义自己的类型。Common Type System的层次结构反映了IL的单继承面向对象方法论。如图1-1所示:
图1-1
下面的表格用来说明上图中所提到的类型。
类 型 | 含 义 |
类型 | 基类,任何类型都继承于他 |
值类型 | 基类,任何值类型都继承于他 |
引用类型 | 通过引用来访问,并且存储在堆中的数据类型 |
内置值类型 | 包括大多数标准基本类型,用来表示数字、布尔类型、字符 |
枚举类型 | 枚举值的集合 |
用户定义值类型 | 在源代码种定义并且保存为值类型的数据类型。在C#语言里,所有的结构体都是这种 |
接口类型 | 接口 |
指针类型 | 指针 |
自我描述类型 | 为垃圾收集器提供自身信息,这样就能利用垃圾收集器带来的好处,这样的类型就是自我描述类型(参看下一节) |
数组 | 包含一批对象的数据类型 |
类类型 | 除了数组之外的自我描述类型 |
委托 | 设计用来为方法保存应用的类型 |
用户定义引用类型 | 在源代码中定义并且保存为引用类型的数据类型。在C#语言里,所有的类都是这种 |
装箱的值类型 | 将临时包装值类型在一个引用中,这样值类型也可以存储在堆中 |
我们这里不回列出所有的内置值类型,因为在第二章我们将要详细的讨论他们。在C#里,每一个预定义类型都会被编译器映射到相应的IL中的内置类型。同样在Visual Basic .NET.中也是这样的。
公共语言规范