1. 泛型的产生
泛型是在.NET 2.0的时候推出来的重要功能。在.NET 1.0时期,因为不支持泛型,所以开发人员在编写一些功能的时候只能以object为参数,然后在方法体内对参数进行转换,
采用这种编程模式容易出现以下问题:
1.效率低下:主要体现在传入的参数为值类型的对象,会涉及到拆箱/装箱的操作(引用类型的AS转换对性能的影响可以忽略不计);
2.容易出错: 采用object作为参数,然后在方法体中进行转换。因为没有其他约束,所以在编译时很难发现问题,只能在运行时才会出现问题;
3.代码可读性不高: 不能通过参数来判断传入的参数类型,只能在后面的转换代码中才能发现,可读性比较低。
而泛型的推出恰好解决了上面提到的问题
2. 创建泛型
泛型主要在类、接口、委托、方法中使用。泛型中的占位符可以有多个,只要不适用保留字就可以。
泛型类创建,语法如下:
class MyGenericClass<T> { //其他代码 }
Framework中的List<T>, Queue<T>, Dictionary<Tkey, Tvalue>等都是常见的泛型类
泛型接口创建,语法如下:
interface MyGenericIntergace<T> { //其他代码 }
Framework中的IList<T>, IDictionary<Tkey, Tvalue>等都是常见的泛型接口
泛型委托创建,语法如下:
delegate void MyGenericGelegate<T>();
Framework中的Action和Func等都是常见的泛型委托
泛型方法创建,语法如下:
public void SetName<T>(T name) { //其他代码 }
3.使用泛型
泛型对象和普通对象在使用方面基本是一致的。例如:
MyGenericClass<int> t1 = new MyGenericClass<int>(); //创建泛型类的实例 t.SetName<string>("Demo"); //调用泛型方法
在.NET中编译器是足够智能的,能够根据给定的参数类型进行推算出占位符的类型。因此在这种情况下可以省略占位符。例如:
t.SetName<string>("Demo"); //占位符的类型为string t.SetName("Demo"); //编译器能够根据参数类型为string自动推算出占位符为string,这个时候就可以省略占位符。效果和上面是一样的,但是书写更简单
4.泛型约束
如果没有泛型约束,那泛型的提出是没有任何意义的。因为没有指定任何约束的话,那么传入的参数T只能判断为是object类型,如果需要用到指定对象的方法,同样只能是进行类型转换,那这和不使用泛型是没有任何区别的。正是因为泛型约束的存在,才能够更好的利用泛型进行编程。
泛型主要提供了以下几种约束:
- 结构约束
- 类约束
- 构造函数约束
- 继承约束
- 接口约束
1.结构约束:类型参数必须是值类型,如下所示:
class MyGenericClass<T> where T : struct { //其他代码 }
此时T必须是int, datetime, struct等值类型的数据
2.类约束:类型参数必须是引用类型,如下所示
class MyGenericClass<T> where T : class { //其他代码 }
此时T必须是引用类型的对象
3.构造函数约束:类型参数必须有无参数的构造函数,如下所示:
class MyGenericClass<T> where T : new() { //其他代码 }
此时T必须有无参数的构造函数,可以是默认的,也可以是非默认的。泛型约束不能约束有参数的构造函数。
4.继承约束:类型参数必须继承自某个给定的基类
class MyGenericClass<T> where T : BaseClass { //其他代码 }
此时T必须是BaseClass或者是其子类。继承约束在泛型约束中是比较常用的,因为指定了继承的基类,那么在方法体中就可以调用基类的成员对象,就可以根据多态进行自由的控制。继承约束最多只能指定一个基类(C#中的类最多只能继承一个基类,不支持多继承)
5.接口约束:类型参数必须实现指定的接口,如下所示:
class MyGenericClass<T> where IEnumerable<T>{ //其他代码 }
此时T必须实现指定的接口。接口约束在泛型约束中也是比较常用的。并且因为C#支持实现多个接口,所以接口约束是可以指定多个的,为编写代码提供了很大的便利。
在反省约束中,可以同时指定多种约束,只要不矛盾即可,利用我们可以约束类型参数同时继承自某个基类,并且实现给定的接口。但是结构约束和类约束是不能同时指定的。很简单,因为不可能要求一个对象既是值类型,同时还是引用类型。
5.default默认值
在泛型中,因为类型参数是不固定的(值类型/引用类型),因此在对象创建以及返回值和普通方法有所区别。例如:在未指定类型参数为引用类型的话,方法的返回值不能设置为null,因为有可能为值类型。
为了解决这个问题,.NET Framework针对泛型的对象初始化方法,使用default关键字。
使用default创建的对象遵循以下规则:
- 如果对象为值类型,则返回该值类型对象的默认值,例如int类型返回为0,DateTime类型返回的是当前的时间;
- 如果对象为引用类型,则返回null(不会去调用无参数的构造函数)