19.C#2.0介绍
C#2.0引入了几项语言扩展,其中最重要的是泛型、匿名方法、迭代器和不完整类型(partial type)。
泛型可以让类、结构、接口、委托和方法,通过他们所存储和操纵的数据的类型被参数化。泛型是很有用的,因为他们提供了更强的编译时类型检查,减少了数据类型之间的显式转换,以及装箱操作和运行时类型检查。
匿名方法可以让代码块以内联的方式潜入到期望委托值的地方。匿名方法与Lisp 编程语言中的λ函数(lambda function)相似。C#2.0支持“closures”的创建,在其中匿名方法可以访问相关局部变量和参数。
迭代器是可以递增计算和产生值的方法。迭代器让类型指定foreach语句如何迭代它的所有元素,变得很容易。
不完整类型可以让类、结构和接口被拆分成多个部分存储在不同的源文件中,这更利于开发和维护。此外,不完整类型允许某些类型的机器生成的部分与用户编写的部分之间的分离,因此增加由工具产生的代码很容易。
本章将介绍这些新特征。介绍完之后,接下来的四章提供了这些特征的完整的技术规范。
C#2.0的语言扩展主要被设计用于确保与现存的代码之间最大的兼容性。例如,尽管C#2.0对于where、yield 和partial这些词在特定上下文中赋予了特别的意义,但这些词仍然可被用作标识符。实际上,C# 2.0没有增加任何可能与现有代码中的标识符冲突的关键字。
19.1 泛型
泛型可以让类、结构、接口、委托和方法,通过他们所存储和操纵的数据的类型被参数化。C#泛型对于使用Eiffel或Ada的泛型的用户,或者对于C++模板的用户来说是很熟悉的;但他们将不用再去忍受后者的众多的复杂性。
19.1.1为什么使用泛型
没有泛型的话,通用目的的数据结构可以采用object类型存储任何类型的数据。例如,下面的Stack类在一个object数组中存储数据,而它的两个方法,Push和Pop相应地使用object接收和返回数据。
public class Stack
{
object[] items;
int count;
public void Push(object item){…}
public object Pop(){…}
}
尽管使用类型object可以使得Stack类更加灵活,但这样做也并不是没有缺点。例如,你可以将一个任何类型的值,诸如,Customer的一个实例压入(Push)堆栈。但当你取回一个值时,Pop方法的结果必须被显式地强制转换到合适的类型,为一个运行时类型检查去编写代码,以及带来的性能不利影响,是很令人讨厌的。
Stack stack = new Stack();
Stack.Push(new Customer());
Customer c = (Customer)stack.Pop();
如果一个值类型的值,例如一个int被传递到Push方法,它将会被自动装箱。当后面获得这个int 时,它必须使用一个显式的强制转换而被取消装箱。
Stack stack = new Stack();
Stack.Push(3);
int I = (int)stack.Pop();
这种装箱和取消装操作增加了性能开销,因为它们涉及到动态内存的分配和运行时类型检查。
Stack类的更大的问题是,它不能强制放置在堆栈上的数据种类。实际上,Customer实例可以被压入堆栈,而取回它时可能被强制转换到错误的类型。
Stack stack = new Stack();
Stack.Push(new Customer());
String s = (string)stack.Pop();
尽管先前的代码是Stack类的一种不恰当用法,但这段代码从技术上说是正确的,并且也不会报告编译时错误。问题直到代码执行时才会冒出来,在这一点上将会抛出一个InvalidCastException异常。
如果Stack类具有能够指定其元素的类型能力,那么很显然它能从这种能力得到好处。使用泛型,这将会变成可能。
19.1.2 创建和使用泛型
泛型为创建具有类型参数(type parameter)的类型提供了工具。下面的例子声明了一个带有类型参数T的泛型Stack类。类型参数在类名字之后的“<“和“>”分界符中指定。这里没有object与别的类型之间的相互转换,Stack<T>的实例接受它们被创建时的类型,并且存储那个类型的数据而没有转换它。类型参数T充当一个占位符,直到使用的时候才指定一个实际的类型。注意,T被用作内部items数组的元素类型、Push方法参数的类型和Pop方法的返回值类型。
Public class Stack<T>
{
T[] items;
int count;
public void Push(T item){…}
public T Pop(){…}
}
当泛型类Stack<T>被使用时,T所代替的实际类型将被指定。在下面的例子中,int 将被作为T的类型参数而给出。
Stack<int> stack = new Stack<int>();
Stack.Push(3);
int x = stack.Pop();
Stack<int>类型被称为构造类型(constructed type)。在Stack<int>类型中,T的每次出现都被使用类型参数int代替。当Stack<int>的实例被创建时,items数组的本地存储就是一个int[]而不是object[],与非泛型Stack相比,它提供了更高的存储效率。同样地,在int值上的Stack<int>操作的Push和Pop方法,将会使得压入其他类型的值到堆栈中出现一个编译时错误,并且当取回值的时候也不需要转换回它们原始的类型。
泛型提供了强类型,意义例如压入一个int到Customer对象堆栈将会出现错误。就好像Stack<int>被限制只能在int值上操作,同样Stack<Customer>也被限制用于Customer对象。
对于下面的例子,编译器将会在最后两行报告错误。
Stack<Customer> stack = new Stack<Customer>();
Stack.Push(new Customer());
Customer c = stack.Pop();
stack.Push(3); //类型不匹配错误
int x = stack.Pop(); //类型不匹配错误
泛型类型声明可以有任意数量的类型参数。先前的Stack<T>例子 只有一个类型参数,但一个通用的Dictionary类可能有两个类型参数,一个用于键(key)的类型,另一个用于值(value)的类型。
public class Dictionary<K , V>
{
public void Add(K key , V value){…}
public V this[K key]{…}
}
当Dictionary<K , V> 被使用时,必须提供两个类型参数。
Dictionary<string , Customer> dict = new Dictionary<string , Customer>();
Dict.Add(“Peter”, new Customer());
Custeomer c = dict[“Perter”];
19.1.3泛型类型实例化
与非泛型类型相似,被编译过的泛型类型也是由中间语言[Intermediate Language(IL)]指令和元数据表示。泛型类型的表示当然也对类型参数的存在和使用进行了编码。
当应用程序首次创建一个构造泛型类型的实例时,例如,Stack<int>,.N