通过使用泛型,我们可以极大地提高代码的重用度,同时还可以获得强类型的支持,避免了隐式的装箱、拆箱,在一定程度上提升了应用程序的性能。
泛型类实例化的理论
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#高级编程第六版》