前言:
为什么需要泛型
因为泛型很好的避免了成员膨胀以及类型膨胀。
正交性
将编程实体看成是横轴,泛型看成是纵轴的话,那么他们都会有一个交叉点。下图反应了泛型和编程实体的正交性,于是产生了泛型类,泛型接口…等等。
泛型 | |
---|---|
类 | Yes |
接口 | Yes |
方法 | Yes |
属性 | Yes |
字段 | Yes |
… | Yes |
一,泛型解决了装箱拆箱的问题。
装修拆箱过程中需要进行大量的计算。有效的减小装箱拆箱的过程是提高性能的一个好的途径,为此微软公司提供了泛型解决方案来解决装箱拆箱引起的性能问题。
下面先通过一个例子来说明这个问题,代码如下:
namespace GenericExercise
{
class Program
{
static void Main(string[] args)
{
RnUnbox(); //装箱拆箱的方法
RunNoUnbox(); //采用泛型的方法
Console.ReadLine();
}
private static void RnUnbox()
{
DateTime startTime = DateTime.Now; //方法开始执行时间
ArrayList myArrayList = new ArrayList();
for (int i = 0; i < 5; i++)
{
for (int count = 0; count < 5000000; count++)
{
myArrayList.Add(count); //装箱
}
int j = 0;
for (int count = 0; count < 5000000; count++)
{
j=(int)myArrayList[count]; //拆箱
}
}
DateTime endTime = DateTime.Now;
Console.WriteLine("使用装箱拆箱的数组--开始时间为:{0},结束时间为:{1},花费时间为{2}",startTime,endTime,endTime-startTime);
}
private static void RunNoUnbox()
{
DateTime startTime = DateTime.Now;
List<int> myTlist = new List<int>(); //泛型数组
for (int i = 0; i < 5; i++)
{
for (int count = 0; count < 5000000; count++)
{
myTlist.Add(count);
}
int j = 0;
for (int count = 0; count < 5000000; count++)
{
j = myTlist[count];
}
}
DateTime endTime = DateTime.Now;
Console.WriteLine("使用泛型的数组--开始时间为:{0},结束时间为:{1},花费时间为{2}", startTime, endTime, endTime - startTime);
}
}
}
运行结果如下:
上述代码包括两个方法。做的都是相同的工作:将5000000条记录(int类型的数据记录)装进容器里,然后再读取出来,反复进行五次。所不同的是,RnUnbox()方法采用的是ArrayList作为容器,而RunNoUnbox()采用的是List<>作为容器。从运行时间上来看,两个方法的运行时间差别较大。这是为什么呢 ?
下面来分析一下:
public virtual int Add(object value);这是ArrayList中的Add()方法,其函数参数是object类型的,object是引用类型的。因此当我们要添加的元素是值类型的时候(比如本例),那么就会发生装箱操作。当我们读取ArrayLiist中的数据的时候,又会发生拆箱操作,因此会耗费大量时间。
第二种方法采用了 List< T > 这种泛型集合。来看看它的元素添加方法。public void Add(T item),它的函数参数是T类型的。我们在申明的时候已经将T特化成int类型了,因此,不存在装箱拆箱的过程。
泛型类
小菜,大鸟相遇了。
大鸟:一店主卖商品,他会为商品准备盒子。
小菜:现再假设一个商店卖苹果,并且,每次顾客买苹果的话,店主都会送一个盒子,创建一个苹果类,一个盒子类:
//苹果类
class Apple
{
public string Color{get;set;}
}
//盒子类
class Box
{
public Apple Cargo{get;set;} //准备苹果这种货物
}
大鸟:现在,商店里还要卖书了,而且买书的时候,店主也会提供盒子
小菜:那我就在原来的基础上,创建书的类,装书的盒子:
//苹果类
class Apple
{
public string Color{get;set;} //苹果的颜色
}
//盒子类,装苹果
class AppleBox
{
public Apple Cargo{get;set;} //准备苹果这种货物
}
//书类
class Book
{
public string Name{get;set;} //书名。
}
//盒子类,装书
class BookBox
{
public Book Cargo{get;set;} //准备书这种货物
}
大鸟:小菜,试想一下,如果商店还需要卖其他东西的时候,怎么办?
小菜:这个简单啊,需要什么样的盒子,我就创建什么样类型的盒子类啊。
大鸟:如果,有上千种的话, 那岂不是要创建上千个盒子类了。这就造成了类型膨胀。而且随着商品的增加,代码的改动就越来越多了。想想还有没有其他的解决方案?
小菜:我可以创建一个盒子类,然后让这个盒子可以放各种商品。代码表示如下:
//苹果类
class Apple
{
public string Color{get;set;} //苹果的颜色
}
//书类
class Book
{
public string Name{get;set;} //书名。
}
//盒子类
class Box
{
public Apple apple{get;set;} //准备苹果这种货物
public Book book{get;set;} //准备书这种货物
//...其他商品....
}
大鸟:这样的话,也是可以的。这段代码在显现实生活中可以这么理解:你生产了一个盒子,盒子里的空间被分成 了很多的独立空间,每个空间对应的可以存放一种商品。那么,试想一下,当你的这个盒子的一个空间用来存放苹果的话,其他人的盒子空间是空着的,这就造成了浪费。对于盒子类来说就是成员膨胀。
小菜:那我该怎么呢?
大鸟:现在假设你有一个盒子,开始你是不知道这个盒子要装什么的,当决定给这个盒子贴上苹果标签的时候,那么它就是苹果盒子。当你贴上书籍标签的时候,它就是装书的盒子。
小菜:那在编程的世界里,我怎么来实现这种标签呢?
大鸟:这就是我们今天要说的泛型类了,代码如下:
//苹果类
class Apple
{
public string Color{get;set;} //苹果的颜色
}
//书类
class Book
{
public string Name{get;set;} //书名。
}
//盒子类 ( **泛型类** )
class Box<T>
{
public T Cargo{get;set;} //T类型的货物(泛型属性)
}
现在来模拟一下商品交易的过程:
static void Main(string[] args)
{
Apple apple = new Apple() { Color="Red"};//一苹果,颜色Red
Book book = new Book() { Name="New Book"};//一本书,名字New Book
Box<Apple> box1 = new Box<Apple>() { Cargo=apple}; //苹果盒子
Box<Book> box2 = new Box<Book>() {Cargo=book };//书盒子
Console.WriteLine(box1.Cargo.Color);
Console.ReadLine();
}
这样的话,一个盒子类,一个货物属性就能满足要求了。在调用类的时候,进行盒子类型的特化。
小菜:嗯呢。。。。懂了。