通过使用泛型,我们可以极大地提高代码的重用度,同时还可以获得强类型的支持,避免了隐式的装箱、拆箱,在一定程度上提升了应用程序的性能。
泛型类实例化的理论
C#泛型类在编译时,先生成中间代码IL,通用类型T只是一个占位符。在实例化类时,根据用户指定的数据类型代替T并由即时编译器(JIT)生成本地代码,这个本地代码中已经使用了实际的数据类型,等同于用实际类型写的类,所以不同的封闭类的本地代码是不一样的。按照这个原理,我们可以这样认为:
泛型类的不同的封闭类是分别不同的数据类型。
为什么要有泛型
它可以避免重复代码。如果有几个类或者方法类似,实现功能相同,区别只是类型不一样,如int和string是,这是可以定义一个泛型。
泛型类就类似于一个模板,可以在需要时为这个模板传入任何我们需要的类型。
由一个例子引入:
我们在算法中会学到排序,一个很经典的排序为冒泡排序法。将几个乱序的数字用冒泡发排序,我想大多数人会很快的写出来。
我将例子弄详细一点。新建一张aspx的页面,放一个button和一个label服务器控件,双击button,自动生成响应方法。网页关键代码如下:
1 <asp:Button ID="Button1" runat="server" Text="对1,8,3,7,2排序" onclick="Button1_Click" />
2 <asp:Label ID="Label1" runat="server" Text=""></asp:Label>
要实现排序,需要现有实现冒泡排序的类,我们新建一个ShortHelper类:
1 public class ShortHelper {
2 public void BubbleSort(int[] array)
3 {
4 int length = array.Length;
5 for(int i=0;i<=length-2;i++)
6 for (int j = length-1; j >= 1; j--)
7 {
8 if (array[j] < array[j - 1])
9 {
10 int temp = array[j];
11 array[j] = array[j - 1];
12 array[j - 1] = temp;
13 }
14 }
15 }
16 }
将相应函数补充完整:
protected void Button1_Click(object sender, EventArgs e)
{
string endsort = "";
ShortHelper shorter = new ShortHelper();
int[] array = { 1, 8, 3, 7, 2 };
shorter.BubbleSort(array);
foreach (int i in array)
{
endsort += i.ToString() + ",";
}
int length = endsort.Length - 1;
endsort = endsort.Substring(0, length);
Label1.Text = endsort;
}
结果如下:

但是,我现在又想对英文字母或者byte进行排序,怎么办呢,当然直接修改类型是可以的。但是这样会造成代码的不简洁,重用性差。现在我们就引入泛型,泛型其实就是类似于C++中的模版,是一种解决方法的同法,将类型用一个类似于占位符的 T 代替。修改后的类如下:
1 public class ShortHelper<T> where T : IComparable {
2 public void BubbleSort(T[] array)
3 {
4 int length = array.Length;
5 for (int i = 0; i <= length - 2; i++)
6 for (int j = length - 1; j >= 1; j--)
7 {
8 if (array[j].CompareTo(array[j - 1]) < 0)//因为object是没有比较大小的方法的,不用IComparable会照成无法比较大小,CompareTo方法用于比较,前一个比后一个小,返回值<0,反之返回>0
9 {
10 T temp = array[j];
11 array[j] = array[j - 1];
12 array[j - 1] = temp;
13 }
14 }
15 }
16 }
在网页中添加
1 <asp:Button ID="Button2" runat="server" Text="对f,a,s,t,v,b,e排序" onclick="Button2_Click" />
2 <asp:Label ID="Label2" runat="server" Text=""></asp:Label>
相应的相应函数为
1 protected void Button1_Click(object sender, EventArgs e)
2 {
3 string endsort = "";
4 ShortHelper<int> shorter = new ShortHelper<int>(); //注意此处
5 int[] array = { 1,8,3,7,2};
6 shorter.BubbleSort(array);
7 foreach (int i in array)
8 {
9 endsort += i.ToString() + ",";
10 }
11 int length = endsort.Length - 1;
12 endsort = endsort.Substring(0, length);
13 Label1.Text = endsort;
14 }
15
16 protected void Button2_Click(object sender, EventArgs e)
17 {
18 string endsort = "";
19 ShortHelper<char> shorter = new ShortHelper<char>(); //注意此处
20 char[] array = { 'f', 'a', 's', 't', 'v', 'b', 'e' };
21 shorter.BubbleSort(array);
22 foreach (char i in array)
23 {
24 endsort += i + ",";
25 }
26 int length = endsort.Length - 1;
27 endsort = endsort.Substring(0, length);
28 Label2.Text = endsort;
29 }
运行结果为:
当然,泛型的用法不仅仅就这,还有泛型接口,泛型方法等等。
优点:
1、性能:避免隐式的装箱和拆箱
如AarryList(在添加数据时装箱,读取时拆箱)和List<T>区别
2、类型安全:
泛型的另一个特性是类型安全。与ArrayList类一样,如果使用对象,可以在这个集合中添加任意类型。下面的例子在ArrayList类型的集合中添加一个整数、一个字符串和一个MyClass类型的对象:
1 ArrayList list = new ArrayList();
2 list.Add(44);
3 list.Add("mystring");
4 list.Add(new MyClass());
如果这个集合使用下面的foreach语句迭代,而该foreach语句使用整数元素来迭代,编译器就会编译这段代码。但并不是集合中的所有元素都可以转换为int,所以会出现一个运行异常:
1 foreach (int i in list)
2 {
3 Console.WriteLine(i);
4 }
错误应尽早发现。在泛型类List<T>中,泛型类型T定义了允许使用的类型。有了List<int>的定义,就只能把整数类型添加到集合中。编译器不会编译这段代码,因为Add()方法的参数无效:
1 List<int> list = new List<int>();
2 list.Add(44);
3 list.Add("mystring"); // compile time error
4 list.Add(new MyClass()); // compile time error
3、二进制代码的重用:
泛型允许更好地重用二进制代码。泛型类可以定义一次,用许多不同的类型实例。
类型参数约束
程序员在编写泛型类时,总是会对通用数据类型T进行有意或无意地有假想,也就是说这个T一般来说是不能适应所有类型,但怎样限制调用者传入的数据类型呢?这就需要对传入的数据类型进行约束,约束的方式是指定T的祖先,即继承的接口或类。因为C#的单根继承性,所以约束可以有多个接口,但最多只能有一个类,并且类必须在接口之前。
除了可以约束类型参数T 实现某个接口以外,还可以约束T 是一个结构、T 是一个类、T 拥有构造函数、T 继承自某个基类等。
泛型方法
在泛型方法中,泛型类型用方法声明来定义。
1 void Swap<T>(ref T x, ref T y)
2 {
3 T temp;
4 temp = x;
5 x = y;
6 y = temp;
7 }
把泛型类型赋予方法调用,就可以调用泛型方法:
1 int i = 4;
2 int j = 5;
3 Swap<int>(ref i, ref j);
但是,因为C#编译器会通过调用Swap方法来获取参数的类型,所以不需要把泛型类型赋予方法调用。泛型方法可以像非泛型方法那样调用:
1 int i = 4;
2 int j = 5;
3 Swap(ref i, ref j);
当一般方法与泛型方法具有相同的签名时,会覆盖泛型方法。
泛型类的特性
1、默认值:不能把null赋予泛型类型。原因是泛型类型也可以实例化为值类型,而null只能用于引用类型。为了解决这个问题,可以使用default关键字。通过default关键字,将null赋予引用类型,将0赋予值类型。
T doc = default(T);
2、约束:如果泛型类需要调用泛型类型上的方法,就必须添加约束。
约 束 | 说 明 |
where T : struct | 使用结构约束,类型T必须是值类型 |
where T : class | 类约束指定,类型T必须是引用类型 |
where T : IFoo | 指定类型T必须执行接口IFoo |
where T : Foo | 指定类型T必须派生于基类Foo |
where T : new() | 这是一个构造函数约束,指定类型T必须有一个默认构造函数 |
where T : U | 这个约束也可以指定,类型T1派生于泛型类型T2。该约束也称为裸类型约束 |
3、继承:泛型类型可以执行泛型接口,也可以派生于一个类。泛型类可以派生于泛型基类:
1 public class Base<T>
2 {
3 }
4 public class Derived<T> : Base<T>{}
4、静态成员:泛型类的静态成员需要特别关注。泛型类的静态成员只能在类的一个实例中共享。
总结:
泛型。通过泛型类可以创建独立于类型的类,泛型方法是独立于类型的方法。增加代码的重用性。接口、结构和委托也可以用泛型的方式创建。泛型引入了一种新的编程方式。
参考资料:
http://www.cnblogs.com/JimmyZhang/archive/2008/12/17/1356727.html,《C#高级编程第六版》