泛型的优点是多方面的,无论是泛型类还是泛型方法都同时具备可重用性、类型安全和高效率等特性,这都是非泛型类和非泛型方法无法具备的。本建议将从可重用性、类型安全和高效率三个方面来阐述:在实际的编码过程中为何总是应该优先考虑泛型。
以可重用性为例,比如要设计一个集合类:
class MyList
{
int[]items;
public int this[int i]
{
get{return items[i];}
set{this.items[i]=value;}
}
public int Count
{
get{return items.Length;}
}
///省略其他方法
}
该类型只支持整型,如果要让类型支持字符串,有一种方法是重新设计一个类。但是这两个类型的属性和方法都是非常接近的,如果有一种方法可以让类型接收一个通用的数据类型,这样就可以进行代码复用了,同时类型也只要一个就够了。泛型完成的就是这样一个功能。以上版本的泛型的实现方法是:
class MyList<T>
{
T[]items;
public T this[int i]
{
get{return items[i];}
set{this.items[i]=value;}
}
public int Count
{
get{return items.Length;}
}
///省略其他方法
}
可以把T理解为一个占位符,在C#泛型编译生成的IL代码中,T就是一个占位符的角色。在运行时,即时编译器(JIT)会用实际代码中输入的T类型来代替T,也就是说,在由JIT生成的本地代码中,已经使用了实际的数据类型。我们可以把MyList<int>和MyList<string>视做两个完全不同的类型,但是,这仅是对本地代码而言的,对于实际的C#代码,它仅仅拥有一个类型,那就是泛型类型MyList<T>。
以上从代码重用性的角度论证了泛型的优点。继续从类型MyList<T>的角度论述,如果不用泛型实现代码重用,另一种方法是让MyList的编码从object的角度去设计。在C#的世界中,所有类型(包括值类型和引用类型)都是继承自object,如果要让MyList足够通用,就需要让MyList针对object编码,代码如下所示:
class MyList
{
object[]items;
public object this[int i]
{
get{return items[i];}
set{this.items[i]=value;}
}
public int Count
{
get{return items.Length;}
}
///省略其他方法
}
这会让下面的代码编译通过:
list[0]=123;
list[1]="123";
由上面两行代码带来的问题就是非“类型安全性”。该问题实际在建议20中已经详细论述过了。让类型支持类型安全,可以让程序在编译期间就过滤掉部分Bug,同时,也能让代码规避掉“转型为object类型”或“从object转型为实际类型”所带来的效率损耗。尤其是涉及的操作类型是值类型时,还会带来装箱和拆箱的性能损耗。
例如,上文代码中的
list[0]=123;
就会带来一次装箱操作,因为它首先被转型为object,继而存储到items这个object数组中去了。
泛型为C#语言带来的是革命性的变化,FCL之后的很多功能都是借助泛型才得到了很好的实现,如LINQ。LINQ借助于泛型和扩展方法,有效地丰富了集合的查询功能,同时避免了代码爆炸并提升了操作的性能。我们在设计自己的类型时,应充分考虑到泛型的优点,让自己的类型成为泛型类。