C#2.0之细说泛型
C#2的头号亮点 : 泛型
在C#1中,Arraylist总是会给人带来困扰,因为它的参数类型是Object,这就让开发者无法把握集合中都有哪些类型的数据。如果对string类型的数据进行算术操作那自然会报错,但是遺憾的是在編譯期不会给你任何的提示
C#2中引入的泛型极其耀眼,甚至有些人会因为泛型而忽略C#2中其它新加入的特性
回到上面的问题,ArrayList带来的不仅仅是上面那些困扰,Object是所有类型的基类,但是我们在开发中它幾乎不会有它的身影,因为它包容一切,但是我们需要使用int做算术操作的时候就发生了问题,就是强制类型转换。当我们需要使用string的subString方法IDE也不会给你任何提示,因为它是Object类型。
泛型带来的就是这样革命性的改变,允許你的参数类型为泛型类型。回到ArrayList,泛型的典型就是List<T>了,而T就是泛型类型,它是一个未綁定类型(不明确的)。但是在我们使用的时候需要进行指定
List<string> strList = new List<string>();
这时我们去访问它的元素时就会有string数据类型的智能提示,我们可以輕松的点出subString方法。这时 List<string>就是已构造 的泛型类型。而且因为是显示的,也不会有类型转换的问题。性能上也是大大的提高,这时我们把运行时需要检查的提前到了编译时
开放封闭类型
这是一个比较绕的概念,看名称比较迷糊其实很容易理解。
泛型类型虽然也是类型,而T就是泛型类型,但它是开放的,可以是string,int,class等。开放类型无法创建实例,因为不知道创建什么实例,而封闭类型可以创建实例,封装类型就是指定了实际的数据类型,比如<string>
泛型约束
泛型类型也不总是任意数据类型都可以的,在开发的时候我们常常需要对传入的实际类型进行把握,如果外部使用时传入了意外的类型,那么就会引发BUG。
引用类型约束
如果我们希望数据类型是引用类型,那么我们可以 where T: class
值类型约束
如果我们希望数据类型是值 类型,那么我们可以 where T: struct
構造函數约束
我们希望数据类型是拥有无参的构造函数,那么我们可以 where T: new()
转换类型约束
我们希望数据类型是可以转换为我们想要指定的数据类型,比如某个基类的子类,某个接口的实现。我们可以 where T: Person
我们还可以根据实际需要进行组合约束,但是需要注意的是不是所有的约束都可以组合在一起,比如 约束不可以既是引用类型约束又是值类型约束。
泛型方法类型实参的类型推断
需要注意的是,这并不是我们常用的var类型推断,而是泛型方法的类型推断。
比如list<string>.Add("小明"); 这时小明很明显是一个字符串,那么这段代码就可以简化成 list.Add("小明");
高级泛型
这部分主要讨论的是泛型的效率问题与反射部分
都知道,相比较arrayList,泛型不会有拆箱装箱的过程,除了这些他们在内存中存儲的方式也是不一样的。比如ArrayList的byte和List<byte>。ArrayList会为每一个字节进行装箱并存儲已装箱值的引用。而List<byte>没有額外的引用存儲。他们看起来如下图
对于静态类型与静态字段还有静态構造函數, 我们都知道他们只会初始化一次,并且之后不会发生改变。对于泛型来说同一个泛型参数类型确实如此 ,每个封闭类型都有自己的静态字段集。它们是独立的。
比如 List<int> a;
List<string> b;
List<object > c;
List<int> d;
我们可以看到a与d是一个泛型参数类型,它们是静态构造函數只会运行一次,但是b、c、a会走三次,因为类型不一样。
JIT编译器处理泛型的时候,会把值类型翻译成不同的本地码,而引用类型翻译成共享的本地码。因为引用具有相同的大小。而值类型并不是。
我们在使用foreach的时候会感到很便利,有时候我们也会自己实现一个迭代器,这很简单,只要去实现IEnumerable<T>接口与IEnumerator<T>接口即可,但是如果我们发现泛型的接口又实现着非泛型的接口。如果没有问题,泛型接口都应该继承一个对应的非泛型接口,这样就可以实现协变性,在之前的C#版本中也会适用。
泛型存在的不足
C#的泛型设计的非常的巧妙,但是也有一些存在的不足之处。
泛型可变性(协变、逆变)的缺乏
在面向对象开发中我们可以用子类去New基类,这是没有问题的,但是对于泛型来说是不可以的,可以看到代码第一行是报错的。泛型不支持可变性,它们是不变体,这是为了类型安全着想
但是我们可以通过其它方法来实现可变性。最简单的使用泛型接口,和编写輔助类。在4.0中有了其它的解决方法
缺乏操作符约束
我们想要对泛型实参进行算术运算,但是并不是所有的类型都可以进行算术运行的。也没有类似于 + - / *这种操作符的约束。我们可以使用表达式树和动态特性解决此问题
缺乏泛型属性、索引器、成员类型
这很直观 ,上面所说的都无法使用泛型,我们不可以
Public T Property<T>{get;set;}
当然这个是无解的。因为至少我们开发的时候不会有这样的需求