泛型是.net 2.0就有的特性,泛型在我们的平常的开发过程中使用得也非常多,为了更深刻地理解泛型,这篇文章就来总结一下。
什么是泛型
可以用下面2点来概括:
1,首先泛型也是一种类型(可以通过IL代码看出来)。
2,泛型提供了类型参数化的能力,允许用不同的类型进行实例化,可以简单地理解为:泛型是类型的模板。
为什么要使用泛型
下面通过一个demo来演示不使用泛型和使用泛型的实现,进而理解为什么要使用泛型。
不使用泛型:
1 /// <summary> 2 /// 普通方法,使用ArrayList 3 /// </summary> 4 public static void MyNoGenericMethod() 5 { 6 ArrayList arrayList = new ArrayList(); 7 arrayList.Add(1);//产生装箱 8 arrayList.Add(2);//产生装箱 9 arrayList.Add("3");//编译时没有提示错误 10 11 var result = 0; 12 foreach (int item in arrayList) 13 { 14 result += item;//运行时错误 15 } 16 }
使用泛型:
1 /// <summary> 2 /// 泛型,使用List<T> 3 /// </summary> 4 /// <returns></returns> 5 public static void MyGenericMethod() 6 { 7 List<int> list = new List<int>(); 8 list.Add(1); 9 list.Add(2); 10 //list.Add("3");//编译时就提示错误 11 12 var result = 0; 13 foreach (int item in list) 14 { 15 result += item; 16 } 17 }
从上面这个例子可以看到,使用泛型一是保证了类型安全,二是提高了性能,因为避免了装箱和拆箱。
我们再来看一个例子,实现了一个int类型的栈,如下代码:
1 /// <summary> 2 /// int类型的Stack 3 /// </summary> 4 public class MyIntStack 5 { 6 int stackPointer = 0; 7 int[] stackArray; 8 9 /// <summary> 10 /// 入栈 11 /// </summary> 12 /// <param name="x"></param> 13 public void Push(int x) 14 { 15 16 } 17 18 /// <summary> 19 /// 出栈 20 /// </summary> 21 /// <returns></returns> 22 public int Pop() 23 { 24 25 } 26 }
假如要将数据类型改成float,就需要将代码copy一份并且将数据类型改成float,这样代码就重复了,时间长了很难维护,那有没有一种方法不需要copy代码呢?
假如引入了泛型,就不需要重复copy代码了,如下代码:
1 /// <summary> 2 /// 泛型的stack 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public class MyStack<T> 6 { 7 int stackPointer = 0; 8 T[] stackArray; 9 10 /// <summary> 11 /// 入栈 12 /// </summary> 13 /// <param name="x"></param> 14 public void Push(T x) 15 { 16 17 } 18 19 /// <summary> 20 /// 出栈 21 /// </summary> 22 /// <returns></returns> 23 public T Pop() 24 { 25 26 } 27 }
这样,下次再换成double类型都没有问题,所以这里体现了泛型带来的,代码复用的好处。
所以,综合上面两个例子,我们可以总结出,使用泛型可以带来以下好处:
1,代码(算法)复用。
2,类型安全。
3,提高性能,避免了装箱和拆箱。
4,扩展性。
泛型的本质
反编译上面示例的IL代码,如下图:
![](https://images2015.cnblogs.com/blog/311549/201706/311549-20170616175943446-1648978867.png)
从上图的IL代码可以看出:
1,泛型其实也是一个类(class)。
2,泛型类型生成IL代码后跟普通类型的区别是,使用了占位符'<T>'。
泛型类型
声明泛型类型跟声明普通类型差不多,不同的地方在于,泛型在类名后面放一对尖括号,并且使用了类型参数。
泛型类型的创建过程如下图。
![](https://images2015.cnblogs.com/blog/311549/201706/311549-20170619164400304-1879971157.png)
声明泛型类型:
1 /// <summary> 2 /// 声明泛型类 3 /// </summary> 4 /// <typeparam name="T1"></typeparam> 5 /// <typeparam name="T2"></typeparam> 6 public class MyGenericClass<TRequest, TResponse> 7 where TRequest : class 8 where TResponse : class 9 { 10 11 }
创建构造类型和实例:
1 //创建构造类型和实例 2 var item = new MyGenericClass<Request, Response>();
完整代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 namespace ConsoleApplication11 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 //创建构造类型和实例 8 var item = new MyGenericClass<Request, Response>(); 9 10 } 11 } 12 13 /// <summary> 14 /// 声明泛型类 15 /// </summary> 16 /// <typeparam name="T1"></typeparam> 17 /// <typeparam name="T2"></typeparam> 18 public class MyGenericClass<TRequest, TResponse> 19 where TRequest : class 20 where TResponse : class 21 { 22 23 } 24 25 public class Request 26 { 27 28 } 29 30 public class Response 31 { 32 33 } 34 }
泛型方法
泛型方法可以在泛型和非泛型类以及结构和接口中声明,如下图。
![](https://images2015.cnblogs.com/blog/311549/201706/311549-20170619164517491-1198541666.png)
1,声明泛型方法
声明泛型方法要注意两点,封闭在圆括号里的方法参数,和封闭在尖括号里的类型参数,并且可以在方法参数后面放可选的约束子句。
如下代码:
1 /// <summary> 2 /// 泛型方法 3 /// </summary> 4 /// <typeparam name="T">类型参数</typeparam> 5 /// <param name="input">方法参数</param> 6 public static void MyGenericMethod<T>(T input) where T : class 7 { 8 9 }
2,调用泛型方法
1 var rq= new Request(); 2 MyGenericMethod<Request>(rq);//原始调用
3,推断类型
因为编译器可以从我们传入的方法参数中推断出类型参数,所以有了推断类型我们的调用更简单,如下代码:
1 MyGenericMethod(rq);//推断类型
完整代码如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 namespace ConsoleApplication13 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 //泛型方法 8 var rq= new Request(); 9 MyGenericMethod<Request>(rq);//原始调用 10 11 MyGenericMethod(rq);//推断类型 12 13 } 14 15 /// <summary> 16 /// 泛型方法 17 /// </summary> 18 /// <typeparam name="T">类型参数</typeparam> 19 /// <param name="input">方法参数</param> 20 public static void MyGenericMethod<T>(T input) where T : class 21 { 22 23 } 24 25 } 26 27 public class Request 28 { 29 30 } 31 }
泛型接口
泛型接口的声明跟普通接口的声明差不多,不同的地方在于,泛型接口需要在接口名称之后的尖括号中有类型参数。
1,声明泛型接口
如下代码:
1 /// <summary> 2 /// 泛型接口 3 /// </summary> 4 /// <typeparam name="T">类型参数</typeparam> 5 public interface IMyGenericInterface<T> 6 { 7 void ReturnIt(T input); 8 9 }
2,实现泛型接口
1 /// <summary> 2 /// 泛型接口实现 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 public class MyGenericInterfaceImpl<T> : IMyGenericInterface<T> where T:class 6 { 7 public void ReturnIt(T input) 8 { 9 10 } 11 }
3,完整调用
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 namespace ConsoleApplication14 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 var rq = new Request(); 8 var m = new MyGenericInterfaceImpl<Request>(); 9 m.ReturnIt(rq); 10 11 } 12 } 13 14 /// <summary> 15 /// 泛型接口 16 /// </summary> 17 /// <typeparam name="T">类型参数</typeparam> 18 public interface IMyGenericInterface<T> 19 { 20 void ReturnIt(T input); 21 22 } 23 24 /// <summary> 25 /// 泛型接口实现 26 /// </summary> 27 /// <typeparam name="T"></typeparam> 28 public class MyGenericInterfaceImpl<T> : IMyGenericInterface<T> where T:class 29 { 30 public void ReturnIt(T input) 31 { 32 33 } 34 } 35 36 public class Request 37 { 38 39 } 40 }
泛型委托
泛型委托的声明跟普通委托的声明也差不多,不同的点在于,泛型委托包括委托形参和类型参数。
1,声明泛型委托
如下代码:
1 namespace ConsoleApplication15 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 //调用泛型委托 8 var myGenericDel = new MyGenericDel<string>(Simple.PrintString); 9 myGenericDel += Simple.PrintUpperString; 10 11 myGenericDel("Hello generic delegate!"); 12 } 13 } 14 15 /// <summary> 16 /// 声明泛型委托 17 /// </summary> 18 /// <typeparam name="T"></typeparam> 19 /// <param name="input"></param> 20 public delegate void MyGenericDel<T>(T input); 21 22 public class Simple 23 { 24 /// <summary> 25 /// 声明方法匹配委托 26 /// </summary> 27 /// <param name="input"></param> 28 public static void PrintString(string input) 29 { 30 Console.WriteLine(input); 31 } 32 33 /// <summary> 34 /// 声明方法匹配委托 35 /// </summary> 36 /// <param name="input"></param> 37 public static void PrintUpperString(string input) 38 { 39 Console.WriteLine(input.ToUpper()); 40 } 41 42 } 43 }
2,Linq与泛型委托
说起泛型委托,就不得不提Linq中的泛型委托,.net 3.5为我们带来了Linq和lambda表达式。
这三个泛型委托在linq中经常用到。
1)Action委托
action表示无返回值的泛型委托。
如下代码:
1 /// <summary> 2 /// Action泛型委托 3 /// </summary> 4 /// <typeparam name="T"></typeparam> 5 /// <param name="action"></param> 6 public static void TestAction<T>(Action<T> action, T input) 7 { 8 action(input); 9 }
2)Func委托
func表示有返回值的泛型委托。
如下代码:
1 //Func委托 2 var result = list.Where(p => p < 10).ToList();
3)Predicate委托
predicate表示返回bool值的泛型委托。
如下代码:
1 //Predicate委托 2 var result2 = list.Find(p => p == 100);
完整代码:
1 namespace ConsoleApplication16 2 { 3 class Program 4 { 5 static void Main(string[] args) 6 { 7 //Linq与泛型委托 8 var list = Enumerable.Range(0, 1000).ToList(); 9 10 //Action委托 11 TestAction<int>(p => Console.WriteLine(p), 10); 12 13 //Func委托 14 var result = list.Where(p => p < 10).ToList(); 15 16 //Predicate委托 17 var result2 = list.Find(p => p == 100); 18 19 Console.ReadKey(); 20 } 21 22 /// <summary> 23 /// Action泛型委托 24 /// </summary> 25 /// <typeparam name="T"></typeparam> 26 /// <param name="action"></param> 27 public static void TestAction<T>(Action<T> action, T input) 28 { 29 action(input); 30 } 31 32 } 33 }
泛型结构
//不是很常用,暂时不总结。