引入
如下代码,如果我们想用一个方法来代替该怎么办呢?
/// <summary> /// 打印个int值 /// </summary> /// <param name="iParameter"></param> public static void ShowInt(int iParameter) { Console.WriteLine("This is {0},type={1},parameter={2}", typeof(CommonMethod).Name, iParameter.GetType().Name, iParameter); } /// <summary> /// 打印个string值 /// </summary> /// <param name="sParameter"></param> public static void ShowString(string sParameter) { Console.WriteLine("This is {0},type={1},parameter={2}", typeof(CommonMethod).Name, sParameter.GetType().Name, sParameter); } /// <summary> /// 打印个DateTime值 /// </summary> /// <param name="oParameter"></param> public static void ShowDateTime(DateTime dtParameter) { Console.WriteLine("This is {0},type={1},parameter={2}", typeof(CommonMethod).Name, dtParameter.GetType().Name, dtParameter); }
很自然,我们会想到用object作为传入参数的类型,因为object是一切类的基类,这样,我们就可以通过一个方法来实现传入多种类型,如
public static void ShowObject(object oParameter) { Console.WriteLine("This is {0},type={1},parameter={2}", typeof(CommonMethod), oParameter.GetType().Name, oParameter); }
但是,使用object来实现时,可能会存在以下问题:
1.装箱拆箱会有性能损耗
比如传入一个int值时,在装箱拆箱时,需要在堆和栈之间进行操作。
2.类型安全问题,传递的对象没有限制,可能会有安全问题。如需要传入一个people,但是传入一个animal,类型无错误,但编译和运行可能有误。
泛型是什么?
为了解决以上问题,在.net 2.0时,提出了泛型概念,那么什么是泛型呢?
其实,泛型就是为了解决类似于上述问题的,可以说,泛型是用一个事物来代替多种不同类型需求的,泛型的实现采用的是延迟声明的方式,是不同于使用object的继承方式的。那么,泛型的实现具体需要哪些支持呢?
泛型需要的框架支持:
1.编译器能支持类型参数,用占位符(`1、`2...)来表示有几个参数,在运行时确定类型。
2.CLR升级才能支持占位符。运行时,类型确定了,就可以把占位符给替换掉
注意,泛型不是语法糖(什么是语法糖,见文末),而是框架的升级。
泛型的声明和使用
介绍了什么是泛型,我们接着来看,如何定义一个泛型,其实,像List<>,Dictionary<>这样的带有<>的都是泛型声明,如下,是一个自定义的泛型方法
public static void Show<T>(T tParameter) { Console.WriteLine("This is {0},type={1},parameter={2}", typeof(CommonMethod), tParameter.GetType().Name, tParameter); }
如上,在定义时不指定具体类型而使用类似T这样的符号来代替(这里可以是任意的定义,除了关键字)的方式即可声明一个泛型。
泛型声明后,具体该如何使用呢?
因为泛型类型是在运行时解析并确定类型,因此,在调用泛型相关的对象时,需要指定类型。如
CommonMethod.Show<int>(iValue);//调用泛型,需要指定类型参数 CommonMethod.Show(iValue);//如果可以从参数类型推断,可以省略类型参数(这是一种语法糖) CommonMethod.Show<string>(sValue);
综上所述,泛型在定义时不需要指定类型,但是在使用时,除了可从参数推断类型的情况,必须指定类型。可省略类型的情况,其实是一种语法糖,是编译器提供的功能。
泛型方法的性能
泛型方法的性能跟普通方法一致,是最好的,而且能一个方法满足多个不同类型
这里可以通过运行n(n要很大)多次相对应的方法来测试,方法内可以什么也不处理,只指明是普通方法、泛型方法以及object处理即可。
泛型的应用场景
1.泛型方法:为了一个方法满足不同类型的需求
比如:一个方法完成多实体查询、一个方法完成不同类型的数据展示、任意实体,转换成json字符串。
2.泛型类:一个类型满足不同类型需求,如dictionary、list
3.泛型接口:一个接口满足不同类型需求
4.泛型委托
5.泛型缓存
/// <summary> /// 每个不同的T,都会生成一份不同的副本 /// 适合不同类型,需要缓存一份数据的场景,效率高 /// </summary> /// <typeparam name="T"></typeparam> public class GenericCache<T> { static GenericCache() { Console.WriteLine("This is GenericCache 静态构造函数"); _TypeTime = string.Format("{0}_{1}", typeof(T).FullName, DateTime.Now.ToString("yyyyMMddHHmmss.fff")); } private static string _TypeTime = ""; public static string GetCache() { return _TypeTime; } }
这里只需了解什么是泛型缓存即可,之后我们还会讲到,泛型缓存的性能远高于字典缓存(因为泛型缓存在寄存器处理,字典缓存用hash表处理),缺点是只能对某一类型存储一个对象。
从以上场景我们可以看出来,还是那句话,泛型是用一个事物来代替多种类型的需求的。凡是此类情况,即可以考虑用泛型来处理。
明白了什么是泛型,如何定义泛型以及泛型的应用场景, 那我们再来了解一下泛型约束。那么什么是泛型约束?为什么要有泛型约束呢?
泛型约束
俗话说,没有规矩不成方圆,那么规矩就是约束,试想一下,如果没有交通规则,那我们的道路情况是会井井有条还是混乱不堪?其实也就是有约束才有权利、才能更好的工作。那么泛型约束也是一样的道理,是为了让泛型更高效,还记得在开篇的时候,我提到的object处理形式的第二个缺点吗?对,就是类型安全问题,用约束可以完美的解决类型安全问题。talk is cheap,show you the code。看过之后你应该就会明白。
请注意观察以下2段代码的区别:
public static void ShowObject(object oParameter) { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(GenericConstraint), oParameter, oParameter.GetType().Name); People people = (People)oParameter; Console.WriteLine($"{people.Id} {people.Name}"); }
public static void Show<T>(T tParameter) where T : People, ISports, IWork, new() { Console.WriteLine("This is {0},parameter={1},type={2}", typeof(GenericConstraint), tParameter, tParameter.GetType().Name); Console.WriteLine($"{tParameter.Id} {tParameter.Name}"); }
第二段代码定义了一个有泛型约束的方法,其中where T:People...即是泛型的约束定义。
其实以上的主要区别就在类型转换的那一行,在处理前需要先显式转换,而泛型类型则可直接调用,区别在于,泛型约束类型其实已经约束了类型,让编译器知道当前的类型是否匹配。那么此时,如果我们在调用泛型方法时传入一个Animal对象,那肯定会报错的。因为Animal不是People。编译不能通过。
因此,可以说没有约束其实很局限,因为要实现类型转换,加了约束之后,可以不用转换可以直接调用。
既然,泛型约束这有有用,那么泛型约束有哪些种类呢?一般的,我们通常有5大种类的约束。
约束类型
1.基类约束 where T:People(不能是密封类,如where T:String,密封类没有意义,因为没有子类)
2.接口约束 where T:ISport
3.应用类型约束 where T:class
4.值类型约束 where T:struct
5.无参构造函数约束 where T:new()
5种约束可以组合使用,但需要考虑实际情况,比如一般不可能既是class又是struct。默认值用default(T)获取
public T GetT<T, S>() where S : People//基类约束 where S :ISports//接口约束 where T : class//引用类型约束 where T : struct//值类型 where T : new()//无参数构造函数 { //return default(T);//default是个关键字,会根据T的类型去获得一个默认值 return new T(); }
关于泛型可能涉及到一些问题。
问题扩展
1.什么是语法糖?泛型是语法糖吗
语法糖是编译器提供的便捷功能,比如var i=3;以及泛型调用时省略参数类型。语法糖是在编译时确定类型。
泛型不是简单的语法糖,是框架的升级。
2.webservice能用泛型吗?
webservice、WCF都是不能用泛型的,因为服务在发布时类型必须是确定的,而泛型是在运行时确定类型的。
其次,对于这种跨语言的形式,可能别的语言不支持泛型也会有问题。
3.getType()和typeof有区别?
getType是object的一个方法,在对象上调用。
typeof是一个关键字,参数是一个类型