一、泛型的是什么
泛型的英文解释为generic,当然我们查询这个单词时,更多的解释是通用的意思,然而有些人会认为明明是通用类型,怎么成泛型了的,其实这两者并不冲突的,泛型本来代表的就是通用类型,只是微软可能有一个比较官方的此来形容自己引入的特性而已,既然泛型是通用的, 那么泛型类型就是通用类型的,即泛型就是一中模子。 在生活中,我们经常会看到模子,像我们平常生活中用的桶子就是一个模子,我们可以用桶子装水,也可以用来装油,牛奶等等,然而把这些都装进桶子里面之后,它们都会具有桶的形状(水,牛奶和油本来是没有形的),即具有模子的特征。同样,泛型也是像桶子一样的模子,我们可以用int类型,string类型,类去实例化泛型,实例化之后int,string类型都会具有泛型类型的特征(就是说可以使用泛型类型中定义的方法,如List<T>泛型,如果用int去初始化它后,List<int>的实例就可以用List<T>泛型中定义的所有方法,用string去初始化它也一样,和我们生活中的用桶装水,牛奶,油等非常类似
二、C# 2.0为什么要引入泛型
大家通过第一部分知道了什么是泛型,然而C#2.0中为什么要引入泛型的?这答案当然是泛型有很多好处的。下面通过一个例子来说明C# 2.0中为什么要引入泛型,然后再介绍下泛型所带来的好处有哪些。
当我们要写一个比较两个整数大小的方法时,我们可能很快会写出下面的代码:
public class Compare { // 返回两个整数中大的那一项 public static int Compareint(int int1, int int2) { if (int1.CompareTo(int2) > 0) { return int1; } return int2; } }
然而需求改变为又要实现比较两个字符串的大小的方法时,我们又不得不在类中实现一个比较字符串的方法:
如果需求又改为要实现比较两个对象之间的大小时,这时候我们又得实现比较两个对象大小的方法,然而我们中需求中可以看出,需求中只是比较的类型不一样的,其实现方式是完全一样的,这时候我们就想有没有一种类型是通用的,我们可以把任何类型当做参数传入到这个类型中去实例化为具体类型的比较,正是有了这个想法,同时微软在C#2.0中也想到了这个问题,所以就导致了C#2.0中添加了泛型这个新的特性,泛型就是——通用类型,有了泛型之后就可以很好的帮助我们刚才遇到的问题的,这样就解决了我们的第一个疑问——为什么要引入泛型。下面是泛型的实现方法:
public class Compare<T> where T : IComparable { public static T CompareGeneric(T t1, T t2) { if (t1.CompareTo(t2) > 0) { return t1; } else { return t2; } } }
这样我们就不需要针对每个类型实现一个比较方法,我们可以通过下面的方式在主函数中进行调用的:
public class Program { static void Main(string[] args) { Console.WriteLine(Compare<int>.CompareGeneric(3, 4)); Console.WriteLine(Compare<string>.CompareGeneric("abc", "a")); Console.Read(); } }
通过上面的代码大家肯定可以理解C# 2.0中为什么要引入泛型的,然而泛型可以给我们带什么好处的呢?从上面的例子可以看出,泛型可以帮助我们实现代码的重用,大家很清楚——面向对象中的继承也可以实现代码的重用,然而泛型提供的代码的重用,确切的说应该是 “算法的重用”(我理解的算法的重用是我们在实现一个方法中,我们只要去考虑如何去实现算法,而不需要考虑算法操作的数据类型的不同,这样的算法实现更好的重用,泛型就是提供这样的一个机制)。
我们在来看一个熟悉的算法——冒泡排序,冒泡排序中我们可以对不同类型的数据进行排序,其中,基本的算法逻辑是完全相同的,仅仅是数据类型的不同,我们为了适应程序的灵活性,和重用性,我们可以使用泛型来定义一个排序的模子,对多种数据类型进行排序。
class Program { static void Main(string[] args) { int[] array = {12,23,16,32,89,5}; SortHelper<int> sort = new SortHelper<int>(); sort.BubbleSort(array, (a,b) => a > b); Console.ReadKey(); } } public delegate bool Contrast<T>(T t1, T t2);//传入两个参数来作比较 public class SortHelper<T> { public void BubbleSort( T [] array, Contrast<T> contrast) { for (int i = 0; i < array.Length - 1; i++) { for (int j = 0; j < array.Length - 1-i; j++) { if (contrast(array[j] , array[j + 1]) ) { T temp = array[j]; array[j] = array[j+1]; array[j+1] = temp; } } } Console.WriteLine("排序后的数组"); for (int i = 0; i < array.Length - 1; i++) { Console.WriteLine("{0}", array[i]); } } }
运行结果:
然而泛型除了实现代码的重用的好处外,还有可以提供更好的性能和类型安全,下面通过下面一段代码来解释下为什么有这两个好处的。
class Program { public static int constintListSize = 500000; static void Main(string[] args) { UseArrayList(); UseGenericList(); Console.ReadKey(); } private static void UseArrayList() { ArrayList list = new ArrayList(); long startTicks = DateTime.Now.Ticks; for (int i = 0; i < constintListSize; i++) { list.Add(i); } for (int i = 0; i < constintListSize; i++) { int value = (int)list[i]; } long endTicks = DateTime.Now.Ticks; Console.WriteLine("使用ArrayList,耗时:{0} ticks", endTicks - startTicks); } private static void UseGenericList() { List<int> list = new List<int>(); long startTicks = DateTime.Now.Ticks; for (int i = 0; i < constintListSize; i++) { list.Add(i); } for (int i = 0; i < constintListSize; i++) { int value = list[i]; } long endTicks = DateTime.Now.Ticks; Console.WriteLine("使用List<int>,耗时:{0} ticks", endTicks - startTicks); } }
泛型能够提供的另一个好处就是类型安全,这是什么意思呢?看下面一段代码:
ArrayList list = new ArrayList(); int i = 100; list.Add(i); string value = (string)list[0];
有读者一眼就可以看出这段代码有问题,因为类型不匹配,添加到ArrayList中的是一个int类型,而获取时却想将它转换为string类型。 可惜的是,编译器无法知道,因为对它来说,不管是int也好,string也好,它们都是Object类型。 在编写代码时,编译器提供给开发
者的最大帮助之一就是可以检查出错误,也就是常称的编译时错误(Compile timeerror)。 当使用ArrayList时,对于上面的问题,编译器无能为力,因为它认为其是合法的,编译可以顺利通过。 这种错误有时候隐藏在程序中很难发现,最糟糕的情况是产品已经交付用户,而当用户在使用时不巧执行到这段代码,便会抛出一个异常,这时的错误,称为运行时错误(Runtime error)。
通过使用泛型集合,这种情况将不复存在,当试图进行类似上面的转换时,根本无法通过编译,这样有助于尽早发现问题:
List<int> list = new List<int>(); int i = 100; list.Add(i); string value = (string)list[0]; //编译错误