"一次编码,多次使用",这就是引入泛型的根源。在以前的C++中称为模板,C#泛型通过算法和数据结构支持独立编码。例如,泛型列表意味着,你不必再重写一个强类型集合。在本文中,作者将向你展示定义和使用泛型是多么容易的事情-请注意,长期以来泛型一直被认为是最高级和最困难的术语。
一、 简介
泛型现在在任何一种语言中都被认为是一个高级的强有力的术语。当我在C++中第一次接触模板时,我对之有些疑惑。之后,我读了Bjarne Stroustrop的《The Design and Evolution of C++》,才发现模板的使用就象C中的宏和用之来取代的简单串替换模板一样容易。其实,模板和泛型是相同的东西-尽管它们的实现稍微不同。
C#泛型支持在使用点处才定义算法及其数据类型。在C#的一些早期版本中,我们可以证明没有泛型也可以工作,因为每种类型都是派生于一个公共基类型-object。这意味着程序员可以基于object类型定义一个栈类并且把一切东西放到该栈上(因为一切都派生于object)。然而,一个object栈意味着,Customer对象,Integer对象以及假想的对象都能被放置到同一个栈的实例上。结果是,开发者要子类化数据类型来把数据类型绑定到他们要与之交互的东西上去。例如,在编写定制的商业对象时,我们就建议定义派生于System.Collections.CollectionBase的强类型集合。原因很简单:基于object定义一切被认为是弱类型定义。
业界的高手们在数十年前就确信强类型优于弱类型,所以.NET最终支持强类型,这看上去是很自然的事情。强类型算法当然建议类型化参数-这正是我们在泛型中所用的东西。
十几年来,我们一直在使用字母T作为类型化参数的名字。这样,在任何泛型类使用者所提供的数据类型的地方,你都能够找到T。使用泛型的关键仅仅是提供这个T。定义泛型的关键在于实现一个方法或类,并且用特定数据类型来替换掉T。
C#中的泛型支持另外一些提炼。例如,一个方法或类可以有多个参数化的类型并且C#泛型还支持WHERE约束-它用来具体要求类型化参数的类型。例如,如果一个泛型类型必须实现接口IDisposable,那么C#泛型是支持实现这一限制的。在文章的最后我们还要看一下约束问题。
闲话少说,让我们言归正传。
二、 使用泛型集合
有些人问我"面向对象编程(OOP)的承诺在哪里?",我的回答是应该从两个方面来看OOP:你所使用的OOP和你创建的OOP。如果我们简单地看一下如果没有如例如Microsoft的.NET,Borland的VCL,以及所有的第三方组件这样的OO框架,那么很多高级的应用程序几乎就无法创建。所以,我们可以说OOP已经实现了它的承诺。不错,生产好的OOP代码是困难的并且可能是极具挫败性的;但是记住,你不必须一定要通过OOP来实现你的目标。因此,下面首先让我们看一下泛型的使用。
当你用Visual Studio或C# Express等快速开发工具创建工程时,你会看到对于System.Collections.Generic命名空间的参考引用。在这个命名空间中,存在若干泛型数据结构-它们都支持类型化的集合,散列,队列,栈,字典以及链表等。为了使用这些强有力的数据结构,你所要做的仅是提供数据类型。
列表1显示出我们定义一个强类型集合的Customer对象是很容易的。
列表1 这个控制台应用程序包含一个Customer类和一个基于List<T的强类型集合Customers。
using System;
using System.Collections.Generic;
using System.Text;
namespace Generics{
class Program{
static void Main(string[] args){
List<Customer customers = new List<Customer();
customers.Add(new Customer("Motown-Jobs"));
customers.Add(new Customer("Fatman's"));
foreach (Customer c in customers)
Console.WriteLine(c.CustomerName);
Console.ReadLine();
}
}
public class Customer{
private string customerName = "";
public string CustomerName{
get { return customerName; }
set { customerName = value; }
}
public Customer(string customerName){
this.customerName = customerName;
}
}
}
注意,我们有一个强类型集合-List<Customer-对这个集合类本身来说不需要写一句代码。如果我们想要扩展列表customer,我们可以通过从List<Customer继承而派生一个新类。
三、 实现一个泛型类
一种合理的实现某种新功能的方法是在原有的事物上进一步构建。我们已经了解强类型集合,并知道一种不错的用来构建泛型类的技术是使用一个特定类并删除数据类型。也就是说,让我们定义一个强类型集合CustomerList,并且来看一下它要把什么东西转化成一个泛型类。
列表2定义了一个类CustomerList。后面的部分把CustomerList转化成List<T。
列表2定义类CustomerList:
using System;
using System.Collections;
using System.Text;
namespace Generics{
public class CustomerList : CollectionBase{
public CustomerList() { }
public Customer this[int index]{
get { return (Customer)List[index]; }
set { List[index] = value; }
}
public int Add(Customer value)
{return List.Add(value);}
}
}
四、 定义类头
如果我们定义一个泛型类,我们需要把类头转化成一个泛型类。所有我们需要做的是命名参数并且把类名改成某种泛型。List<T只有一个参数T,并且因为我们在以一种向后兼容的方式工作,所以我们知道类名是List。列表3显示出列表2中类的新类头。
列表3 一个泛型类头显示出参数化的参数T。
using System;
using System.Collections;
using System.Text;
namespace Generics{
public class List<T : CollectionBase {}
五、 实现泛型字段
如果我们需要把任何字段转换成泛型字段,我们将只需简单地把它们的类型改变成T(或该字段所描述的任何参数)。泛型List不需要任何字段,但是假定存在一个私有的整型字段叫foo-我们将把它泛型化。我们将如下重新定义它:
private T foo;
当参数T被填充到类中时,List T也将因foo被填充。
六、 定义泛型方法
接下来,我们为所需要的参数化类型定义其它一些特性。这包括属性,方法,和事件。在我们的实例中,在Customer出现的每一处,我们都用参数T替换它。完成后的泛型列表类显示于列表4中。
列表4 一个基于System.Collections.CollectionBase的轻量级的参数化泛型列表类。
using System;
using System.Collections;
using System.Text;
namespace Generics{
public class List<T : CollectionBase {
public List(){ }
public T this[int index] {
get { return (T)List[index]; }
set { List[index] = value; }
}
public int Add(T value) {
return List.Add(value);
}
}
}
为了测试该定制列表,注释掉使用System.Collections.Generic命名空间一句并且把列表4中的List<T使用在列表1的代码中;它将以同样的方式工作。
全面地修改.NET的List<T是不必要的而且它也包含远比我们的示例多得多的特性;但是列表4显示出这种机制对于定义定制泛型类是多么容易。
七、 增加类型约束
最后要讨论的是约束问题。约束被应用于类或其它特性上并且使用下面的语法:
Where T : constraint_type
例如,任何我们想要通过using语句所使用的,如一个SqlDataReader,必须实现Idisposable接口。这是因为如下方式使用的using语句:
using(Form f = new Form()){...}
就象一个try..finally块一样工作-总是清除新创建的资源。其工作原理很简单,只需要CLR针对在该using语句中创建的对象发出一个到IDisposable.Dispose的调用即可。例如,在上面这一句中,一个新的表单被创建,并且在using语句退出之前即调用Form.Dispose。
要对一个泛型类施加以确保该类实现了接口IDisposable,我们将添加先行词where T:Idisposable。在列表4中的泛型列表上施加约束后,我们将重新修改列表4如下面的列表5所示。
列表5 增加一个约束到泛型类以确保我们的List<T中的所有的值T实现接口Idisposable。
using System;
using System.Collections;
using System.Text;
namespace Generics{
public class List<T : CollectionBase where t : IDisposable{
public List(){ }
public T this[int index]{
get { return (T)List[index]; }
set { List[index] = value; }
}
public int Add(T value){return List.Add(value);}
}
}
先行词where的值可以是类,接口,结构,实现一个无参的公共构造器或有一个特定的基类的类。详见有关帮助文档。
八、 总结
泛型的设计是用来减少你重复实现的代码的次数-只需改变数据类型即可。因为抽象数据结构,如队列,栈和列表皆是典型的数据结构,所以存在针对这些东西的泛型类完全可以理解。你可以从.NET中派生大量的值-通过使用现有的泛型类,如在System.Collections.Generic命名空间中的那些。
可以肯定,在一段相当长的时间里,泛型将会象模式和重构等革新一样对开发带来越来越大的价值,而且新的数据结构能被转换成可重用的如泛型等的代码元素。