9.6 泛型简介
.NET 2.0中引入了功能强大的泛型特性,利用泛型,可以减少编码量,提高应用程序运行的效率。.NET Framework 2.0内部也提供了很多泛型类供编程使用。
本节将从解释泛型概念开始,讨论.NET Framework 2.0中提供的集合类的泛型版本。
9.6.1 什么是泛型
泛型就好比Word中的模板,在Word的模板中,提供了基本的文档编辑内容,在定义Word模板时,对具体编辑哪种类型的文档是未知的。在.NET中,泛型则提供了类、结构、接口和方法的模板,泛型也可以看作是占位符,与定义Word模板时类似,定义泛型时的具体类型是未知的。
举个例子:在9.1节中讲述的ArrayList类中,所有的元素类型都为object类型。.NET中object类是所有类的基类,因此,ArrayList类能够接受任何类型的值作为他的元素。
当使用ArrayList中的元素时,必须要进行强制类型转换将元素转换为合适的元素类型。如果元素是值类型的值时,会引起CLR进行拆箱和装箱的操作,造成一定的性能开销。而且,还必须小心处理类型转换中可能出现的错误。在下面的示例代码中,为ArrayList类型的对象al添加了多种不同的类型的元素值。
ArrayList al = new ArrayList();
//添加一个字符串
al.Add("这是一个字符型");
//添加一个整型
al.Add(2);
//添加一个布尔型
al.Add(true);
显然,由于ArrayList元素类型默认为object类型,所以,添加不同类型的元素是可行的,并且看起来也很灵活。但是,在很多场合,应用程序并不需要像上面的代码那样,向一个集合类中添加各种不同的类型。
如果集合类只需要处理同种类型的元素,比如字符型,既然元素都是同一种类型,那么,可以将集合中的元素定义为确定的类型,或称之为强类型。这样,就可以减少类型转换带来的性能开销,而且,也避免了类型转换中可能会出现的错误。下面例子演示了一个强类型示例。
/// <summary>
/// 一个强类型集合类
/// </summary>
public class StringList:CollectionBase
{
public string this[int index]
{
get{ return (string)InnerList[index]; }
set{ InnerList[index] = value; }
}
public void Add(string value)
{
InnerList.Add(value);
}
}
在上面的示例中,强制只能添加string类型。如果一定要添加一个其他类型的话,比如读者一定要运行下面的示例代码:
StringList sl = new StringList();
sl.Add(true);
foreach (string s in sl)
{
Console.WriteLine("强类型集合结果:{0}", s);
}
编译时将弹出一个错误,如图9.18所示。在错误列表中会显示错误的详细信息,如图9.19所示。

图9.18 编译时错误 图9.19 错误列表
这种方式解决了以object作为参数的缺陷,而且工作得也还不错。但是,如果还需要一个强类型整型值、布尔值或其他的类型时,必须一一地实现这些强类型类,这些重复工作显然增加了代码量。
.NET 2.0中引入了泛型来处理这种形式的不足,经由指定一个或多个类型占位符,在处理类型操作时,不用知道具体类型,而将具体类型的指定交由运行时来实现。
9.6.2 一个泛型的示例
要定义一个泛型,需要定义一个类型占位符,或称类型参数,一般以T(Type parameter)来代表类型参数。为了让集合能处理多个类型,而又不用重复编码,可以定义一个泛型集合类型,以类型作为参数。下面的代码演示了类型化集合类的泛型定义。
/// <summary>
/// 集合类的泛型定义
/// </summary>
/// <typeparam name="T">类型参数</typeparam>
public class GenericCollections<T>:Collection<T>
{
}
通过在类名后面使用一对尖括号,中间放一个称为类型参数的T,并且,在索引器和方法声明中,在本来应该放置具体类型的地方放置了类型参数T,这是C#中声明泛型的方法。
VS2005的IntelliSense功能,为泛型的声明和使用提供了强大的支持。在使用刚才定义的泛型类时,只要为类型参数传入一个指定的类型,以后使用类中的方法时,IntelliSense将智能地提示使用相应的类型,提示效果如图9.20所示。下面的代码演示了如何使用刚才定义的泛型集合类。
static void Main(string[] args)
{
GenericCollections<string> gcl = new GenericCollections<string>();
gcl.Add("这是一个字符串的示例");
DisplayResult<string>(gcl);
GenericCollections<bool> gcl2 = new GenericCollections<bool>();
gcl2.Add(true);
DisplayResult<bool>(gcl2);
Console.ReadLine();
}
static void DisplayResult<T>(GenericCollections<T> gcl)
{
foreach (T s in gcl)
{
Console.WriteLine(s.ToString());
}
}
在示例程序中,通过为类型参数传递合适的类型,实现了类型化的集合类型。示例代码的输出结果如图9.21所示。

图9.20 IntelliSense支持 图9.21 泛型示例
9.6.3 集合类的泛型版本
本章介绍了System.Collections命名空间下的很多集合类,这些类中的元素类型都是object类型,在本节开头,讨论了使用object作为元素类型的不足之处,在.NET 2.0中,通过为这些集合类添加泛型版本,避免了在编写应用程序时可能出现的类似问题。
泛型集合类位于System.Collections.Generic命名空间中。与本节已经讨论的集合类相对应的泛型集合类如表9.1所示。
表9.1 泛型集合类与非泛型集合类的对比
|
非泛型类(System.Collections) |
对应的泛型类(System.Collections.Generic) |
|
ArrayList |
List |
|
Hashtable |
Dictionary |
|
Queue |
Queue |
|
Stack |
Stack |
|
SortedList |
SortedList |
泛型集合类与对应的非泛型集合类在构造或者使用方法上有一些变化,下面对每种泛型集合类的用法都单独举一个例子进行说明,请读者注意泛型集合类的使用与非泛型集合类之间的区别。
示例一:使用List<T>替换ArrayList,代码如下所示。
static void Main(string[] args)
{
List<string> ls = new List<string>();
ls.Add("泛型集合元素一");
ls.Add("泛型集合元素二");
ls.Add("泛型集合元素三");
foreach (string s in ls)
{
Console.WriteLine(s);
}
Console.ReadLine();
}
示例二:使用Dictionary<Tkey,Tvalue>,代码如下所示。
Console.WriteLine("Dictinary泛型集合类举例");
Dictionary<string, string> dct = new Dictionary<string, string>();
dct.Add("键一", "值一");
dct.Add("键二", "值二");
dct.Add("键三", "值三");
foreach (KeyValuePair<string, string> kvp in dct)
{
Console.WriteLine("{0}:{1}",kvp.Key, kvp.Value);
}
示例三:使用Queue<T>,代码如下所示。
Console.WriteLine("Queue泛型集合类举例");
Queue<string> que = new Queue<string>();
que.Enqueue("这是队列元素值一");
que.Enqueue("这是队列元素值二");
foreach (string s in que)
{
Console.WriteLine(s);
}
示例四:使用Stack<T>,代码如下所示。
Console.WriteLine("Stack泛型集合类举例");
Stack<string> stack = new Stack<string>();
stack.Push("这是堆栈元素值一");
stack.Push("这是堆栈元素值二");
foreach (string s in stack)
{
Console.WriteLine(s);
}
示例五:使用SortedList<Tkey,Tvalue>,代码如下所示。
Console.WriteLine("SortedList泛型集合举例");
SortedList<string, string> sl = new SortedList<string, string>();
sl.Add("key1", "value1");
sl.Add("key4", "value4");
sl.Add("key3", "value3");
sl.Add("key2", "value2");
foreach (KeyValuePair<string, string> kv in sl)
{
Console.WriteLine("{0}:{1}", kv.Key, kv.Value);
}
上面几个示例的输出结果如图9.22所示。

图9.22 泛型集合类型举例输出结果
9.6.4 使用泛型的建议
在处理集合类时,如果遇到下列情况,则可以考虑使用泛型类。
如果需要对多种类型进行相同的操作处理,则应该使用泛型。
如果需要处理值类型,则使用泛型可以避免装箱拆箱带来的性能开销。
使用泛型可以在应用程序编译时发现类型错误,增强程序的健壮性。
减少不必要的重复编码,使代码结构更加清晰。





