贝壳电子书 > 电脑IT电子书 > c#高级编程(第6版)--第九章 >

第1章

c#高级编程(第6版)--第九章-第1章

小说: c#高级编程(第6版)--第九章 字数: 每页4000字

按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!




CLR 2。0的一个新特性是泛型。在。CLR 1。0中,要创建一个灵活的类或方法,但该类或方法在编译期间不知道使用什么类,就必须以Object类为基础。而Object类在编译期间没有类型安全性,因此必须进行强制类型转换。另外,给值类型使用Object类会有性能损失。

CLR 2。0( 3。5基于CLR 2。0)提供了泛型。有了泛型,就不再需要Object类了。泛型类使用泛型类型,并可以根据需要用特定的类型替换泛型类型。这就保证了类型安全性:如果某个类型不支持泛型类,编译器就会生成错误。

泛型是一个很强大的特性,对于集合类而言尤其如此。 1。0中的大多数集合类都基于Object类型。 从2。0开始提供了实现为泛型的新集合类。

泛型不仅限于类,本章还将介绍用于委托、接口和方法的泛型。

本章的主要内容如下:

●       泛型概述

●       创建泛型类

●       泛型类的特性

●       泛型接口

●       泛型方法

●       泛型委托

●       Framework的其他泛型类型
9。1  概述

泛型并不是一个全新的结构,其他语言中有类似的概念。例如,C++模板就与泛型相当。但是,C++模板和泛型之间有一个很大的区别。对于C++模板,在用特定的类型实例化模板时,需要模板的源代码。相反,泛型不仅是C#语言的一种结构,而且是CLR定义的。所以,即使泛型类是在C#中定义的,也可以在Visual Basic中用一个特定的类型实例化该泛型。

下面介绍泛型的优点和缺点,尤其是:

●       性能

●       类型安全性

●       二进制代码重用

●       代码的扩展

●       命名约定
9。1。1  性能

泛型的一个主要优点是性能。第10章介绍了Systemllections和Systemllections。 Generic命名空间的泛型和非泛型集合类。对值类型使用非泛型集合类,在把值类型转换为引用类型,和把引用类型转换为值类型时,需要进行装箱和拆箱操作。

注意:

装箱和拆箱详见第6章,这里仅简要复习一下这些术语。

值类型存储在堆栈上,引用类型存储在堆上。C#类是引用类型,结构是值类型。很容易把值类型转换为引用类型,所以可以在需要对象(对象是引用类型)的任意地方使用值类型。例如,int可以赋予一个对象。从值类型转换为引用类型称为装箱。如果方法需要把一个对象作为参数,而且传送了一个值类型,装箱操作就会自动进行。另一方面,装箱的值类型可以使用拆箱操作转换为值类型。在拆箱时,需要使用类型转换运算符。

下面的例子显示了Systemllections命名空间中的ArrayList类。ArrayList存储对象, Add()方法定义为需要把一个对象作为参数,所以要装箱一个整数类型。在读取ArrayList中的值时,要进行拆箱,把对象转换为整数类型。可以使用类型转换运算符把ArrayList集合的第一个元素赋予变量i1,在访问int类型的变量i2的foreach语句中,也要使用类型转换运算符:

ArrayList list = new ArrayList();

list。Add(44);   // boxing – convert a value type to a reference type

int i1 = (int)list'0';   // unboxing – convert a reference type to a value type

foreach (int i2 in list)



   Console。WriteLine(i2);   // unboxing



装箱和拆箱操作很容易使用,但性能损失比较大,迭代许多项时尤其如此。

Systemllections。Generic命名空间中的List类不使用对象,而是在使用时定义类型。在下面的例子中,List类的泛型类型定义为int,所以int类型在JIT编译器动态生成的类中使用,不再进行装箱和拆箱操作:

List list = new List();

list。Add(44);   // no boxing – value types are stored in the List

int i1 = list'0';   // no unboxing; no cast needed

foreach (int i2 in list)



   Console。WriteLine(i2);


9。1。2  类型安全

泛型的另一个特性是类型安全。与ArrayList类一样,如果使用对象,可以在这个集合中添加任意类型。下面的例子在ArrayList类型的集合中添加一个整数、一个字符串和一个MyClass类型的对象:

ArrayList list = new ArrayList();

list。Add(44);

list。Add(〃mystring〃);

list。Add(new MyClass());

如果这个集合使用下面的foreach语句迭代,而该foreach语句使用整数元素来迭代,编译器就会编译这段代码。但并不是集合中的所有元素都可以转换为int,所以会出现一个运行异常:

foreach (int i in list)



   Console。WriteLine(i);



错误应尽早发现。在泛型类List中,泛型类型T定义了允许使用的类型。有了List的定义,就只能把整数类型添加到集合中。编译器不会编译这段代码,因为Add()方法的参数无效:

List list = new List();

list。Add(44);

list。Add(〃mystring〃);   // pile time error

list。Add(new MyClass());   // pile time error
9。1。3  二进制代码的重用

泛型允许更好地重用二进制代码。泛型类可以定义一次,用许多不同的类型实例化。不需要像C++模板那样访问源代码。

例如,Systemllections。Generic命名空间中的List类用一个int、一个字符串和一个MyClass类型实例化:

List list = new List();

list。Add(44);

List stringList = new List();

stringList。Add(〃mystring〃);

List myclassList = new List();

myClassList。Add(new MyClass());

泛型类型可以在一种语言中定义,在另一种语言中使用。
9。1。4  代码的扩展

在用不同的类型实例化泛型时,会创建多少代码?

因为泛型类的定义会放在程序集中,所以用某个类型实例化泛型类不会在IL代码中复制这些类。但是,在JIT编译器把泛型类编译为内部码时,会给每个值类型创建一个新类。引用类型共享同一个内部类的所有实现代码。这是因为引用类型在实例化的泛型类中只需要4字节的内存单元(32位系统),就可以引用一个引用类型。值类型包含在实例化的泛型类的内存中。而每个值类型对内存的要求都不同,所以要为每个值类型实例化一个新类。
9。1。5  命名约定

如果在程序中使用泛型,区分泛型类型和非泛型类型会有一定的帮助。下面是泛型类型的命名规则:

●       泛型类型的名称用字母T作为前缀。

●       如果没有特殊的要求,泛型类型允许用任意类替代,且只使用了一个泛型类型,就可以用字符T作为泛型类型的名称。

public class List { }

public class LinkedList { }

●       如果泛型类型有特定的要求(例如必须实现一个接口或派生于基类),或者使用了两个或多个泛型类型,就应给泛型类型使用描述性的名称:

public delegate void EventHandler(object sender; TEventArgs e);

public delegate TOutput Converter(TInput from);

public class SortedList { }

9。2  创建泛型类

首先介绍一个一般的、非泛型的简化链表类,它可以包含任意类型的对象,以后再把这个类转化为泛型类。

在链表中,一个元素引用其后的下一个元素。所以必须创建一个类,将对象封装在链表中,引用下一个对象。类LinkedListNode包含一个对象value,它用构造函数初始化,还可以用Value属性读取。另外,LinkedListNode类包含对链表中下一个元素和上一个元素的引用,这些元素都可以从属性中访问。

public class LinkedListNode



private object value;

public LinkedListNode(object value)



this。value = value;



public object Value



get { return value; }



private LinkedListNode next;

public LinkedListNode Next



get { return next; }

internal set { next = value; }



private LinkedListNode prev;

public LinkedListNode Prev



get { return prev; }

internal set { prev = value; }





LinkedList类包含LinkedListNode类型的first和last字段,它们分别标记了链表的头尾。AddLast()方法在链表尾添加一个新元素。首先创建一个LinkedListNode类型的对象。如果链表是空的,则first和last字段就设置为该新元素;否则,就把新元素添加为链表中的最后一个元素。执行GetEnumerator()方法时,可以用foreach语句迭代链表。GetEnumerator()方法使用yield语句创建一个枚举器类型。

提示:

yield语句参见第5章。

public class LinkedList : IEnumerable



private LinkedListNode first;

public LinkedListNode First



get { return first; }



private LinkedListNode last;

public LinkedListNode Last



get { return last; }



public LinkedListNode AddLast(object node)



LinkedListNode newNode = new LinkedListNode(node);

if (first  null)



first = newNode;

last = first;



else



last。Next = newNode;

last = newNode;



return newNode;



public IEnumerator GetEnumerator()



LinkedListNode current = first;

while (current != null)



yield return current。Value;

current = current。Next;







现在可以给任意类型使用LinkedList类了。在下面的代码中,实例化了一个新LinkedList对象,添加了两个整数类型和一个字符串类型。整数类型要转换为一个对象,所以执行装箱操作,如前面所述。在foreach语句中执行拆箱操作。在foreach语句中,链表中的元素被强制转换为整数,所以对于链表中的第三个元素,会发生一个运行异常,因为它转换为int时会失败。

LinkedList list1 = new LinkedList();

list1。AddLast(2);

list1。AddLast(4);

list1。AddLast(〃6〃);

foreach (int i in list1)



Console。WriteLine(i);



下面创建链表的泛型版本。泛型类的定义与一般类类似,只是要使用泛型类型声明。之后,泛型类型就可以在类中用作一个字段成员,或者方法的参数类型。LinkedListNode类用一个泛型类型T声明。字段value的类型是T,而不是object。构造函数和Value属性也变为接受和返回T类型的对象。也可以返回和设置泛型类型,所以属性Next和Prev的类型是LinkedListNode。

publ

返回目录 下一页 回到顶部 0 0

你可能喜欢的