以下几节中我将对C#2中增加的最重要的特性进行介绍。
1)泛型---作为C#2最重要的新特性(同时也是.NET2.0的CLR中最重要的新特性),泛型实现了类型和方法的参数化。
2)可空类型---值类型没有“值不存在”的概念。有了可空类型之后,就可以表示“缺少一个有意义的值”。
3)委托---虽然委托在CLR的级别上没有任何变化,但C#2使它们使用起来更容易。除了语法得到了一些简化,匿名方法的引入,还引导我们采取更“函数化”的编程风格---这个趋势在C#3中得到了延续。
4)迭代器---虽然一直以来,都可以利用C#的foreach语句来简单地使用迭代器,但C#1中,它实现起来却是一件让人痛苦的事情。C#2编译器能在幕后帮你构建一个状态机,从而隐藏了大量复杂性。
对大多数人来说,泛型将成为C#2最重要的新特性。他们增强了性能,使代码更富有表现力,而且将大量的安全检查从执行时转移到了编译时进行。从根本上说,泛型实现了类型和方法的“参数化”,就像在普通方法调用中,经常要用参数来告诉他们使用什么值。同样,泛型的类型和方法也可以让参数告诉他们使用什么类型。
搞懂泛型的方方面面不是一件容易的事情,这样我还是通过简单的例子来学习泛型。先来看看.NET2.0提供的一个集合类:Dictionary<TKey, TValue>。
using System; using System.Collections.Generic; using System.Text.RegularExpressions; class 泛型字典 { static Dictionary<string, int> CountWords(string text) { //创建单词到频率的新映射,它将有效统计每个单词在一段给定文本中出现的频率 Dictionary<string, int> frequencies; frequencies = new Dictionary<string, int>(); /*将文本分解成单词,对于每个单词,都检查它是否已经存在映射中, 如果是则增加现有计数;否则就为单词赋予一个初始计数1 */ string[] words = Regex.Split(text, @"\W+"); foreach (string word in words) { if (frequencies.ContainsKey(word)) { /*这里需要注意下:负责递增的代码不需要执行到int的强制类型转换,就可以执行加法运算: 取回的值在编译时已经知道是int类型。使计数递增的步骤实际是先对映射的索引器执行一次取值操作, 然后增加,然后对索引器执行赋值操作。*/ frequencies[word]++; } else { frequencies[word] = 1; } } return frequencies; } public static void Main() { Console.Write("Please input the text:"); string text = Console.ReadLine(); Dictionary<string, int> frequencies = CountWords(text); //打印映射中的每个键/值对。 foreach (KeyValuePair<string, int> entry in frequencies) { string word = entry.Key; int frequency = entry.Value; Console.WriteLine("{0}:{1}", word, frequency); } Console.Read(); } }
运行效果如下图:
既然我们已经看了一个例子,首先让我们来看一下Dictionary<TKey, TValue>的真正含义,什么是TKey和TValue,而且为什么要用尖括号把他们封闭起来。有两种形式的泛型:泛型类型(包括类、接口、委托和结构)和泛型方法。两者都是表示API的基本方法(不管是指一个泛型方法还是一个完整的泛型类型)。
类型参数是真实类型的占位符。在泛型声明中,类型参数要放在一对尖括号内,并且要以逗号分隔开。所以在Dictionary<TKey, TValue>中,类型参数是TKey和TValue。在使用泛型类型或方法时,需要使用真实的类型代替,这些真实的类型称为类型实参(type argument),在上述代码中类型实参是string(代替TKey)和int(代替TValue)。
如果没有为任何类型参数提供类型实参,声明的就是一个未绑定泛型类型(unbound generic type)。如果指定了类型实参,该对象就成为一个已构造类型(constructed type)。我们知道,类型(无论是否是泛型)可以看做是对象的蓝图。同样,未绑定泛型类型是已构造类型的蓝图,它是一种额外的抽象层。关系如下图:
更复杂的是,已构造类型可以是开放或封闭的。开放类型(open type)还包含了类型参数,而封闭类型(closed type)则不是开放的,类型的每个部分都是明确的。在C#代码中,唯一能看见未绑定泛型的地方(除了作为声明之外)就是在typeof操作符内。类型参数“接收”信息,类型实参“提供”信息。这个思路与方法参数/方法实参是一样的。只不过类型实参必须为类型,而不能为任意的值。只有在编译时才能知道类型实参的类型,它可以是(或包含)相关上下文中的类型参数。
读者可以具体参考下Dictionary<TKey, TValue>的内容,有助于进一步理解,这里就不做啰嗦的说明了。
泛型的发音 在向其他人描述泛型类型时,通常使用of来介绍类型参数或实参,因此List<T>读作list of T。当有多个类型参数时,可以用一个适合整个类型含义的单词来分隔他们,例如:我经常用dictionary of string to int 来强调映射的部分,而不会使用 tuple of string and int。
类型参数命名规范 虽然可以使用带有T、U和V这样的类型参数的类型,但从中根本看不出实际指的是什么,也看不出他们应该如何使用。相比之下,像Dictionary<TKey, TValue>这样的命名就好的多,TKey明显表示keys的类型,而TValue代表values的类型。如果只有一个参数类型,而且它的含义很清楚,那么一般使用T(List<T>就是一个很好的例子)。如果有多个类型参数,则应根据含义来命名,并用T前缀来指明这是一个类型参数。