对.NET平台中泛型技术的探究

2010-09-21 11:30韩志强
赤峰学院学报·自然科学版 2010年11期
关键词:编译器实例代码

韩志强

(赤峰学院计算机科学与技术系,内蒙古赤峰024000)

对.NET平台中泛型技术的探究

韩志强

(赤峰学院计算机科学与技术系,内蒙古赤峰024000)

随着项目变得日益复杂,需要用一个更好的方式来重用和定制现有的软件.为了促进代码重用,尤其是算法的重用,在.NET 2.0以后支持一个名为泛型的特性.方法因为能获得参数而强大.同理,泛型的主旨就是类会因为能获取“类型”参数而变的强大.泛型类使用泛型类型,可以根据需要用特定的类型替换泛型类型,从而在保证了类型安全性的基础上,同时提高了系统性能.泛型不仅限于类,而且还可用于委托、接口和方法等.由此,我们可以体会到泛型是一个强大的特性议.

.NET平台;Object类;泛型;泛型约束

1 为什么引入泛型

若在.NET 2.0以前要创建一个灵活的类或方法,但该类或方法在编译期间不知道使用什么类,就必须以object类为基础,而object类在编译期间没有类型安全性,而且性能也会降低.在这里我们来分析一个没使用泛型的类——System.Collections.Stack.它的作用是表示一个对象集合,使加入集合的最后一项作为从集合中获取的第一项(即后进先出).我们在使用该类时,其收集的是object类型的变量.由于CLR中每个对象都是从object派生的,所以Stack无法验证放在其中的元素是不是你希望的类型.除此之外,若从中获取数据时,必须将返回值转型为我们需要的类型,否则会引发异常.如果我们要使Stack类能支持多个数据类型时,假如不使用泛型,那么根本的问题在于他们必须使用一个通用的基类型,通常是object.因此上述提到的问题是不可避免,同时为使用object的类使用值类型时会使问题进一步恶化.

对此类问题最好的解决方案是——使用泛型.类似的情况还有很多,为了解决此类问题.NET平台引入了泛型的概念.

2 泛型的内部机制

所谓泛型,即通过参数化类型来实现在同一份代码上操作多种数据类型.利用“参数化类型”将类型抽象化,从而实现灵活的复用.由于C#语言是专门为.NET平台量身定制的,因此为了更直观的了解泛型技术,我们在下面以一段C#代码为例,来对.NET的泛型技术进行分析说明,见表1.

在上面的程序中Test是一个泛型类.T是要实例化的泛型类型“参数”.如果T被实例化为int型,那么成员变量obj就是int型的;如果T被实例化为string型,那么obj就是string类型的.根据不同的类型,上面的程序显示出不同的值.其编译过程说明如下:

(1)第一轮编译时,编译器只为Test类型产生“泛型版”的IL代码与元数据——并不进行泛型的实例化,T在中间只充当占位符.例如:Test类型元数据中显示的

表1

(2)JIT编译时,当JIT编译器第一次遇到Test时,将用int替换“范型版”IL代码与元数据中的T——进行泛型类型的实例化.例如:Main函数中显示的

(3)CLR为所有类型参数为“引用类型”的泛型类型产生同一份代码;但是如果类型参数为“值类型”,对每一个不同的“值类型”,CLR将为其产生一份独立的代码.因为实例化一个引用类型的泛型,它在内存中分配的大小是一样的,但是当实例化一个值类型的时候,在内存中分配的大小是不一样的.

3 泛型类型概述

3.1 泛型类

在声明泛型类时,需要在一对<>中指定一个类型参数.例如:public class Stack{……}.然后泛型类的实例会收集与变量声明对应的类型,而不需要将收集到的数据项转换为object类型.在具体声明一个变量和实例化之前,类型参数T只是一个占位符,等到具体声明和实例化变量的时候,编译器要求代码指定类型参数.在程序代码中我们可以看到,类型参数T可用于内部变量声明,某方法的参数类型以及某方法的返回类型.

3.2 泛型接口和struct

现在.NET允许在C#语言的所有组成部分中使用泛型,其中包括泛型接口和struct.其声明语法和类使用的语法完全相同.实现接口时,语法与非泛型类的语法是相同的.

对泛型接口的支持对于集合类来说尤其重要,使用泛型最多的地方就是集合类.假如没有泛型,开发者就要依赖System.Collecttions命名空间中的一系列接口.和它们实现的类一样,这些接口只能用于Object类型.因此这些接口要求进出这些集合类的所有访问都执行一次转型.相反,使用泛型接口,就可以避免执行转型,因为参数化的接口能实现更强的编译时绑定.

3.3 泛型委托

委托是类型安全的方法引用.通过泛型委托,委托的参数可以在以后定义.泛型委托提供了一种更灵活的方式来指定以类型安全的方式调用方法.其支持在委托返回值和参数上应用参数类型,这些参数类型同样可以附带合法的约束.

3.4 泛型方法

泛型方法是即使包容类不是泛型类,或者方法包含的类型参数不在泛型类的类型参数列表中,也依然使用泛型的方法.由此我们看出泛型方法既可以包含在泛型类型中,也可以包含在非泛型类型中..NET的泛型机制不支持在除方法外的其他成员(包括属性、事件、索引器、构造器、析构器)的声明上包含类型参数,但这些成员本身可以包含在泛型类型中,并使用泛型类型的类型参数.

4 泛型约束

泛型允许为类型参数定义约束.这些约束强迫类型遵守各种规则..NET泛型要求对“所有泛型类型或泛型方法的类型参数”的任何假定,都要基于“显式的约束”,以维护.NET所要求的类型安全.“显式约束”并非必须,但是如果没有指定“显式约束”,编译器会认为类型参数只有从基类型Object继承的成员,因为Object是所有类型的终极基类,因此泛型类型参数将只能访问System.Object类型中的公有方法.

.NET中允许你为泛型类中的每个类型参数提供一个可选的约束列表,约束声明了泛型要求的类型参数的特征,为了声明一个约束,需要使用where关键字,后跟一对“参数:要求”.其中,“参数”必须是泛型类型中定义的一个参数,而“要求”用于限制类型从中派生的类或接口,或者限制必须存在一个默认的构造器,或者限制使用一个值类型/引用类型约束.

5.NET泛型与Java泛型的对比

Sun完全是在编译器中为Java实现泛型,而不是在JVM(Java虚拟机)中.Sun这样做的目的是为了防止因为使用了泛型而需要分发新的JVM.Java的实现使用了与C++中的“模板”和C#中的“泛型”相似的语法,其中包括类型参数和约束,但是由于它不区分对待值类型和引用类型,所以未修改的JVM不能为值类型支持泛型.所以Java中的泛型不具有C#那样的执行效率.Java编译器需要返回数据的时候,都会插入来自指定约束的自动向下转型(如果声明了这样的一个转型的话),或者插入基本object类型(如果没有声明的话).除此之外,Java编译器在编译时生成一个具体化的类型,它随即用于实例化任何已构造的类型.最后由于JVM没有提供对泛型的原生支持,所以在执行时无法确定一个泛型类型实例的类型参数,“反射”的其他应用也受到了严格的限制.

6 结束语

.NET中的的泛型能力由CLR在运行时支持,它既不同于C++在编译时所支持的静态模板,也不同于Java在编译器层面使用“擦拭法”支持的简单的泛型,因此.NET的泛型即避免了C++静态模板可能导致的代码膨胀的问题,又提高了执行效率.同时.NET的泛型类型也携带有丰富的元数据,因此.NET的泛型类型可以应用于强大的反射技术.

总之,泛型显著改变了原有的编程风格.凡是以前使用了object类的地方,现在最好使用泛型来代替.换言之,凡是出现了object类的地方,都应该考虑泛型.泛型安全性的提升、转型的避免以及代码量的减少为泛型赋予了无穷的魅力.同理,凡是使用了System.Collections命名空间的地方,System.Collections.Generics都是一个更好的选择.

TP31

A

1673-260X(2010)11-0023-02

猜你喜欢
编译器实例代码
基于相异编译器的安全计算机平台交叉编译环境设计
创世代码
创世代码
创世代码
创世代码
完形填空Ⅱ
完形填空Ⅰ
通用NC代码编译器的设计与实现
基于ARM嵌入式平台的x86译码SOC架构设计