所谓泛型,即通过参数化类型实现在同一份代码上操作多种类型的数据,泛型编程是一种范式的转化(在这里体现为类型的晚绑定),他利用参数化类型,将类型抽象化,从而实现代码的灵活复用,精简代码。

注:1.NET参数化类型不是编译(JIT编译)时被实例化,而是运行时被实例化。

       2.由微软在产品文档中提出建议,所有的泛型参数名称都以T开头,这是作为一种编码的通用规范。

在定义泛型时,可以对客户端代码在实例化类时用于类型参数的类型施加一些限制,如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译错误,这些限制称为约束,约束是使用where关键字实现的。

每个泛型参数至少拥有一个主约束,泛型的主约束是指指定泛型参数必须是或者继承自某个引用类型。每个泛型参数可以具有多个次约束,次约束和主约束的语法基本相同,但它规定的是某个泛型参数必须实现所有次约束指定的接口。

下面列出了五种类型的约束:

T:struct    类型参数必须为值类型,可以指定除 Nullable 以外的任何值类型。
   T:class     类型参数必须为引用类型,包括类、接口、委托、和数组。
   T:new()    类型参数必须具有无参公共构造函数,当与其他约束一起使用时,new() 约束必须最后指定。
   T:<基类名>  类型参数必须为指定的基类或继承自该基类的子类。
   T:<接口名称> 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。
   T:U              为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束.

我们来实现一个最简单的冒泡排序(Bubble Sort)算法,如果你没有使用泛型的经验,我猜测你可能会毫不犹豫地写出下面的代码来,因为这是大学教程的标准实现:

public class Sort{
public void BubbleSort(int[] array) {  
    for (int i = 0; i <= array.Length - 2; i++) {
        for (int j = array.Length - 1; j >= 1; j--){                      if (array[j] < array[j - 1]) {
                int temp = array[j];
                array[j] = array[j - 1];
                array[j - 1] = temp;
            }
        }
    }
  }}

后来我们需要对一个byte类型的数组进行排序,而上面排序的方法只能对int型的数组进行排序,因此我们不得不重写代码:

public class Sort{
public void BubbleSort(byte[] array) {  
    for (int i = 0; i <= array.Length - 2; i++) {
        for (int j = array.Length - 1; j >= 1; j--){                      if (array[j] < array[j - 1]) {
                byte temp = array[j];
                array[j] = array[j - 1];
                array[j - 1] = temp;
            }
        }
    }
  }}

现在我们将int[]和byte[]用占位符来替代,形成一种通用的代码:

public class Sort{
public void BubbleSort(T[] array) {  
    for (int i = 0; i <= array.Length - 2; i++) {
        for (int j = array.Length - 1; j >= 1; j--){                      if (array[j] < array[j - 1]) {
                T temp = array[j];
                array[j] = array[j - 1];
                array[j - 1] = temp;
            }
        }
    }
  }}

但是我们又发现了一个问题:当我们定义一个类,而这个类需要引用它本身以外的其他类型时,如何将这个类型参数传进来了,此时就需要使用一种特殊的语法来传递这个T占位符,我们在类名称的后面加了一个尖括号,使用这个尖括号来传递我们的占位符,也就是类型参数。

public class Sort<T>{
public void BubbleSort(T[] array) {  
    for (int i = 0; i <= array.Length - 2; i++) {
        for (int j = array.Length - 1; j >= 1; j--){                      if (array[j] < array[j - 1]) {
                T temp = array[j];
                array[j] = array[j - 1];
                array[j - 1] = temp;
            }
        }
    }
  }}

使用的时候我们就可以这样使用:

public class Test{
  public static void Main(){
     Sort<int> sorter = new Sort<int>();
     int[] array = { 8, 1, 4, 7, 3 };
     sorter.BubbleSort(array);  }
}

上面所讲述的一切都是一个泛型的典型应用,可以看到,通过使用泛型,我们极大地减少了重复代码,使我们的程序更加清爽,泛型类就类似于一个模板,可以在需要时为这个模板传入任何我们需要的类型。

下面我们来谈一下泛型约束

实际上,如果你运行一下上面的代码,发现他们无法通过编译,为什么了,就是因为有了T的存在,T是晚绑定的,因此在编译时编译器无法得知T的实例是采用什么样的标准来进行大小的比较的,下面我们举例说明:

假如我们有一个自定义的类Book,它定义了书,它包含两个私有字段_id和_title,两个外部属性:ID和Title,以及两个构造器

public class Book
{
   private int _id;
   private string _title;
   public Book(){ }
   public Book(int id,string title)
    {
        this._id=id;
        this._title=title
    }
    public int ID
    {
       get{return _id;}
       set{_id=value;}
    }  
    public string  Title
    {
       get{return _title;}
       set{_title=value;}
    }
}
现在我们创建一个Book型的数组,然后用Sort类中的方法对其进行排序:
class Test{
   static void Main()
   {
       Book[] bookArray=new Book[2];
        Book book1=new Book(1,"guowenhui");
        Book book2=new Book(2,"dongyaguang");
        bookArray[0]=book1;
        bookArray[1]=book2;
        Sort<Book>  sort=new Sort<Book>;
        Sort.BubbleSort(bookArray);
        foreach (Book b in bookArray) {
        Console.WriteLine("Id:{0}", b.Id);
       Console.WriteLine("Title:{0}\n", b.Title);
      }   }
}

可能你觉得这样很好,基本没什么问题,但是我们来看看BubbleSort()方法的实现,我截取关键的一段:

for (int j = array.Length - 1; j >= 1; j--){          
            if (array[j] < array[j - 1]) {
                T temp = array[j];
                array[j] = array[j - 1];
                array[j - 1] = temp;
            }
        }

大家会看到if语句里面会对数组里面的两个元素进行比较,那么问题就出在这儿,以前当类型为int型的时候,我们直接这样进行比较,无可厚非,但是现在不同了,我们的类型是Book,那么我要问,book1和book2到底谁大了,有的人说book1大,有的人说book2大,这里就涉及到一个判断依据的问题。那么如何来实现这种比较了,答案是:让需要进行比较的类实现IComparable接口。也就是说只有实现了IComparable接口的类型才能作为类型参数被传入,即我们需要对传入参数的类型进行一些约束,这就是我们要讲的泛型约束,在本例中我们实现的是接口约束。

接下来我们就让Book类来实现IComparable接口,即在类的内部定义一个比较的标准,我们这里采用的标准是比较ID:

public class Book : IComparable
{
   public int CompareTo(object obj)  //实现接口
   {
     Book book2=(Book)obj;   
     return this.ID.CompareTo(book2.ID);
   }
   private int _id;
   private string _title;
   public Book(){ }
   public Book(int id,string title)
    {
        this._id=id;
        this._title=title;
    }
    public int ID
    {
       get{return _id;}
       set{_id=value;}
    }  
    public string  Title
    {
       get{return _title;}
       set{_title=value;}
    }
}

现在我们应该可以进行比较了吧,还不行,因为Sort类是一个泛型类,JIT编译时编译器对于传入该类的类型参数一无所知(类型的晚绑定),明确的说需要等到运行时才能确定参数,也不会做任何猜想,虽然我们知道Book类实现了

IComparable接口,但编译器并不知道,因此我们必须Sort<T>类(即告诉JIT编译器),它所接受的类型参数必须实现了IComparable接口,这便是泛型约束,下面我们来对泛型类Sort<T>的传入参数进行约束,同时我们对比较大小的方法进行一些修改。

public class Sort<T> where T : IComparable
{
 public void BubbleSort(T[] array) {  
    for (int i = 0; i <= array.Length - 2; i++) {
        for (int j = array.Length - 1; j >= 1; j--){                      if (array[j].CompareTo(array[j-1])<0) {
                T temp = array[j];
                array[j] = array[j - 1];
                array[j - 1] = temp;
            }
        }
    }
  }}

此时我们再次运行下面定义的代码

class Test{
 
   static void Main()
 
   {
 
       Book[] bookArray=new Book[2];
 
        Book book1=new Book(1,"guowenhui");
 
        Book book2=new Book(2,"dongyaguang");
 
        bookArray[0]=book1;
        bookArray[1]=book2;
        Sort<Book>  sort=new Sort<Book>();
        Sort.BubbleSort(bookArray);
        foreach (Book b in bookArray) {
        Console.WriteLine("Id:{0}", b.ID);
       Console.WriteLine("Title:{0}\n", b.Title);
      }      Console.ReadLine();
   }
}
会得到结果:
ID:1
Title:guowenhui
ID:2
Title:dongyaguang

下面是完整的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
public class Book : IComparable
    {
public int CompareTo(object obj)  //实现接口
   {
       Book book2 = (Book)obj;   
return this.ID.CompareTo(book2.ID);
   }
private int _id;
private string _title;
public Book() { }
public Book(int id, string title)
    {
this._id=id;
this._title = title;
    }
public int ID
        {
get { return _id; }
set { _id = value; }
        }
public string Title
        {
get { return _title; }
set { _title = value; }
        }
    }

public class Sort<T> where T : IComparable
    {
public void BubbleSort(T[] array)
        {
for (int i = 0; i <= array.Length - 2; i++)
            {
for (int j = array.Length - 1; j >= 1; j--)
                {
if (array[j].CompareTo(array[j - 1]) < 0)
                    {
                        T temp = array[j];
                        array[j] = array[j - 1];
                        array[j - 1] = temp;
                    }
                }
            }
        }
    }
class Test
    {

static void Main()

   {

       Book[] bookArray=new Book[2];

        Book book1=new Book(1,"guowenhui");

        Book book2=new Book(2,"dongyaguang");

        bookArray[0]=book1;

        bookArray[1]=book2;

        Sort<Book> sort = new Sort<Book>();

        sort.BubbleSort(bookArray);

foreach (Book b in bookArray) 
        {
          Console.WriteLine("Id:{0}", b.ID);
           Console.WriteLine("Title:{0}\n", b.Title);

        }
        Console.ReadLine();

   }

    }
}

泛型接口

没有泛型接口,每次试图使用一个非泛型接口(如IComparable)来操纵一个值类型时,都会进行装箱,而且会丢失编译时的类型安全性。这会严重限制泛型类型的应用。所以,CLR提供了对泛型接口的支持。一个引用类型或值类型为了实现一个泛型接口,可以具体指定类型实参;另外,一个类型也可以保持类型实参的未指定状态来实现一个泛型接口。来看一些例子: