泛型类
泛型类封装不是特定于具体数据类型的操作。泛型类最常用于集合,如链接列表、哈希表、堆栈、队列、树等,其中,像从集合中添加和移除项这样的操作都以大体上相同的方式执行,与所存储数据的类型无关。
对于大多数需要集合类的方案,推荐的方法是使用.NET Framework 2.0类库中所提供的类。
一般情况下,创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡。创建您自己的泛型类时,需要特别注意以下事项:
将哪些类型通用化为类型参数。
一般规则是,能够参数化的类型越多,代码就会变得越灵活,重用性就越好。但是,大多的通用化会使其他开发人员难以阅读或理解代码。
如果存在约束,应对类型参数应用什么约束
一个有用的规则是,应用尽可能最多的约束,但仍使您能够处理需要处理的类型。例如,如果您的泛型仅用于引用类型,则应用类约束。这可以防止您的类被意外地用于值类型,并允许您对T使用as运算符以及检查空值。
是否将泛型行为分解为基类和子类。
由于泛型可以作为基类使用,此处适用的设计注意事项与非泛型类相同。
是否实现一个或多个泛型接口。
例如,如果您设计一个类,该类将用于创建基于泛型的集合中的项,则可能需要实现一个接口,如IComparable<T>,其中T是您的类的类型。
类型参数和约束的规则对于泛型类行为有几方面的含义,特别是关于继承和成员可访问性。请务必先理解一些术语,然后再继承进行。对于泛型类Node<T>,客户端代码可以通过指定类型参数引用该类,以创建封闭式构造类型(Node<int>),或者可以让类型参数处于未指定状态(例如在指定泛型基类时)以创建开放式构造类型(Node<T>)。泛型类可以从具体的、封闭式构造或开放式构造基类继承:
class BaseNode { }
class BaseNodeGeneric<T> { }
// concrete type
class NodeConcrete<T> : BaseNode { }
//closed constructed type
class NodeClosed<T> : BaseNodeGeneric<int> { }
//open constructed type
class NodeOpen<T> : BaseNodeGeneric<T> { }
非泛型(具体)类可以从封闭式构造基类继承,但无法从开放式构造类或裸类型参数继承,因为在运行时客户端代码无法提供实例化基类所需的类型变量。
//No error
class Node1 : BaseNodeGeneric<int> { }
//Generates an error
//class Node2 : BaseNodeGeneric<T> {}
//Generates an error
//class Node3 : T {}
从开放式构造类型继承的泛型类必须为任何未被继承类共享的基类类型参数提供类型变量,如以下代码所示:
class BaseNodeMultiple<T, U> { }
//No error
class Node4<T> : BaseNodeMultiple<T, int> { }
//No error
class Node5<T, U> : BaseNodeMultiple<T, U> { }
//Generates an error
//class Node6<T> : BaseNodeMultiple<T, U> {}
从开放式构造类型继承的泛型类必须指定约束,这些约束是基类类型约束的超集或暗示基类型约束:
class NodeItem<T> where T : System.IComparable<T>, new() { }
class SpecialNodeItem<T> : NodeItem<T> where T : System.IComparable<T>, new() { }
泛型类型可以使用多个类型参数和约束,如下所示:
class SuperKeyType<K, V, U>
where U : System.IComparable<U>
where V : new()
{ }
开放式构造类型和封闭式构造类型可以用作方法参数:
void Swap<T>(List<T> list1, List<T> list2)
{
//code to swap items
}
void Swap(List<int> list1, List<int> list2)
{
//code to swap items
}
泛型类是不变的。也就是说,如果输入参数指定List<BaseClass>,则当您试图提供List<DerivedClass>时,将会发生编译时错误。
泛型接口
为泛型集合类或表示集合中项的泛型类定义接口通常很有用。对于泛型类,使用泛型接口十分可取,例如使用IComparable<T>而不使用IComparable,这样可以避免值类型的装箱和取消装箱操作。.NET Framework 2.0类库定义了若干新的泛型接口,以用于System.Collections.Generic命名空间中新的集合类。
将接口指定为类型参数的约束时,只能使用实现此接口的类型。下面的代码示例显示从GenericList<T>类派生的SortedList<T>类。SortedList<T>添加了约束where T : IComparable<T>。这将使SortedList<T>中的BubbleSort方法能够对列表元素使用泛型CompareTo方法。在此示例中,列表元素为简单类,即实现IComparable<Person>的Person。
public class GenericList < T > : System.Collections.Generic.IEnumerable < T > {
protected Node head;
protected Node current = null;
//Nested class is also generic on T
protected class Node{
public Node next;
private T data; //T used private member datatype
public Node(T t){ //T used in non-generic constructor
next = null;
data = t;
}
public Node Next{
get{
return next;
}
set{
next = value;
}
}
public T Data{ //T as return type of property
get{
return data;
}
set{
data = value;
}
}
}
public GenericList(){ //constructor
head = null;
}
public void AddHead(T t){ //T as method parameter type
Node n = new Node(t);
n.Next = head;
head = n;
}
//Implementation of the iterator
public System.Collections.Generic.IEnumerator<T> GetEnumerator(){
Node current = head;
while(current != null){
yield return current.Data;
current = current.Next;
}
}
//Ienumerable<T> inherits from IEnumerable, therefore this class
//must implement both the generic and non-generic versions of
//GetEnumerator. In most cases, the non-generic method can
//simply call the generic method.
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator(){
return GetEnumerator();
}
}
public class SortedList < T > : GenericList < T > where T : System.IComparable < T > {
//A simple, unoptimized sort algorithm that
//orders list elements from lowest to highest:
public void BubbleSort(){
if (null == head || null == head.Next){
return;
}
bool swapped;
do{
Node previous = null;
Node current = head;
swapped = false;
while (current.next != null){
//Because we need to call this method, the SortedList
//class is constrained on IEnumerable<T>
if (current.Data.CompareTo(current.next.Data) > 0){
Node tmp = current.next;
current.next = current.next.next;
tmp.next = current;
if (previous == null){
head = tmp;
}
else{
previous.next = tmp;
}
previous = tmp;
swapped = true;
}
else{
previous = current;
current = current.next;
}
}
}while(swapped);
}
}
// A simple class that implements IComparable<T> using itself as the
// type argument. This is a common design pattern in objects that
// are stored in generic lists.
public class Person : System.IComparable < Person > {
string name;
int age;
public Person(string s,int i){
name = s;
age = i;
}
//This will cause list elements to be sorted on age values.
public int CompareTo(Person p){
return age - p.age;
}
public override string ToString(){
return name + ":" + age;
}
//Must implement Equals.
public bool Equals(Person p){
return (this.age == p.age);
}
}
class Program {
static void Main(){
//Declare and instantiate a new generic SortedList class.
//Person is the type argument.
SortedList<Person> list = new SortedList<Person>();
//Create name and age values to initialize Person objects.
string[] names = new string[]{
"Franscoise",
"Bill",
"Li",
"Sandra",
"Gunnar",
"Alok",
"Hiroyuki",
"Maria",
"Alessandro",
"Raul"
};
int[] ages = new int[]{45,19,28,23,18,9,108,72,30,35};
//Populate the list.
for (int x = 0; x < 10; x++){
list.AddHead(new Person(names[x],ages[x]));
}
//Print out unsorted list.
foreach(Person p in list){
System.Console.WriteLine(p.ToString());
}
System.Console.WriteLine("Done with unsorted list");
//Sort the list
list.BubbleSort();
//Print out sorted list.
foreach(Person p in list){
System.Console.WriteLine(p.ToString());
}
System.Console.WriteLine("Done with sorted list");
}
}
可将多重接口指定为单个类型上的约束,如下所示:
class Stack<T> where T : System.IComparable<T>, IEnumerable<T>
{
}
一个接口可定义多个类型参数,如下所示:
interface IDictionary<K, V>
{
}
类之间的继承规则同样适用于接口:
interface IMonth<T> { }
interface IJanuary : IMonth<int> { } //No error
interface IFebruary<T> : IMonth<int> { } //No error
interface IMarch<T> : IMonth<T> { } //No error
//interface IApril<T> : IMonth<T, U> {} //Error
如果泛型接口为逆变的,即仅使用其类型参数作为返回值,则此泛型接口可以从非泛型接口继承。在.NET Framework类库中,IEnumerable<T>从IEnumerable继承,因为IEnumerable<T>仅在GetEnumerator的返回值和当前属性getter中使用T。
具体类可以实现已关闭的构造接口,如下所示:
interface IBaseInterface<T> { }
class SampleClass : IBaseInterface<string> { }
只要类型参数列表提供了接口必需的所有参数,泛型类便可以实现泛型接口或已关闭的构造接口,如下所示:
interface IBaseInterface1<T> { }
interface IBaseInterface2<T, U> { }
class SampleClass1<T> : IBaseInterface1<T> { } //No error
class SampleClass2<T> : IBaseInterface2<T, string> { } //No error
对于泛型类、泛型结构或泛型接口中的方法,控制方法重载的规则相同。