最近在学习.net3.0的相关新特性,在这之前又重温了一下2.0的新特性(之后会写一篇浅谈.net3.0的新特性),温故而知新,这是个永恒不变的学习的发方法,也是因为在第一次接触一个新东西所了解的东西,过了一段时间,再去重新学习,你会更深刻的理解、体会以前懂的,而且还会发现以前不懂的东西。不说废话了,下面是我学习的总结,基本上是按照用法与机制说的:
一.泛型
1. 泛型的由来
原来我们使用的数组是初始化的时候就要指定它的长度,这给程序造成了极大的约束与不便,所以.net引入了ArrayList可变长数组,通过ArrayList实例的Add(Object obj)添加数据元素,这样就可以依据实际情况动态的加减的数组的长度。可是注意一下,Add方法参数接收的是一个Object类型的参数,如果我们初始化这个数组为值类型,如int ,double类型的话添加元素时就要将传进去的元素进行装箱,读取数据的时候还要做拆箱操作,从而耗费了系统资源。此外,接收Object类型参数还存在一个弊端,看下面这段代码:
编译并运行上段代码没有问题,如果把上面注释的那一行的注释去掉,再编译,没问题,运行,就出错了,指定的转换无效,因为第三个元素15.0并不是整型的,在循环的时候就发生了运行时异常,这是我们不想看到的。这就说明ArrayList是类型不安全的。那么能不能有一种办法避免这个异常,能在编译时就给我们报一个错误呢?答案是肯定的.net2.0利用泛型解决了这个问题。它自带了一个泛型类List,我们可以用它的时候指定泛型类的类型。
2. 泛型的用法
泛型是这样定义的:所谓泛型,即通过参数化类型来实现在同一份代码上操作多种数据类型。简单地说泛型什么类型都可以。
这就像上面提到的.net内置泛型List,在用到它的时候指定它的类型,若向其添加的元素不是它实例化时指定的类型,编译器就会报错,不会在运行时发生异常,同时初始化的同时,它的类型已经指定了,在添加和读取数据的时候就不会像ArrayList那样装箱拆箱了,节省了不必要的开销。上面的例子如果用泛型来实现就是这样的。
List对象初始化为int类型以后,就已经规定了添加到这个集合的元素只能是int类型的,那么list.Add(15.0)编译时就会报错,从而避免了循环里面的运行时错误。
.net2.0的泛型可用于类,接口,也可以作为父类
Class C<T>{}//定义了一个类型参数
class C<U,V>{}//两个类型参数
class D:C<string,int>{}//D继承自C,C已经被实例化了
class E<U,V>:C<U,V>,//父类使用了子类的参数
class F<U,V>:C<string,int>//子类使用父类已经初始化的
class G:C<U,V>{}//非法
这里面的T,C,U,V实际上没有真正的意义,只是一个占位符而已,看了下一部分“机制”,就会完全的理解。
3. 机制
C#泛型能力由CLR在运行时支持(多语言的都可以用)
C#泛型的编译机制
第一轮编译时,编译器中为泛型类型产生“泛型版”的IL代码与元数据,并不进行泛理类型的实例化,T在中间只充当占位符。看了下面的一段简单代码和编译程序就会得到证实了,
我定义了一个非常简单的泛型类
此泛型类的Il代码如下:
那么说它由CLR支持的是指只要是CLR支持的语言,如VB.NET也可以用C#泛型编写的类。正因为编译后的中间代码是泛型的。
它的运行时支持其实就是一种晚绑定,它将变量声明的类型推迟到使用它的时候。当JIT编译时,当JIT编译器第一次遇到List<int>时,将用int替换泛型版代码与元数据中的T进行泛型类型的实例化。也就是说,一个类型具体是什么类型是在实例化的时候才确定的,这个特点更是优于其它如非托管C++,Java这样的高级语言的。非托管C++是编译时模板机制。在运行时看不到泛型,看到的是具体的一个一个的类型。而JAVA没有在虚拟机层次做改动,在编译器支持泛型,编译器做了类型安全的控制,因此这种泛型得不到更高的效益,C++,Java编译后的代码都将用到泛型的地方就将类型复制一份,所以它们容易引起代码爆炸。而CLR为所有类型参数为“引用类型”的泛型类型产生同一份代码,即是.net编译后还是一个泛型,
4.泛型约束
约束方式:采用“基类,接口,构造器,值类型/引用类型”来实现对类型参数的“显示约束”。
目的,防止运行时错误,发生异常。因为它是运行时,
class A{public void f1(){...}}
class B{public void f2(){...}}
class C<S,T>where S:A
where T:B
{}
可以在S的基础上调用f1..}这样在编译时就会对C的S,T进行检查,不会在运行时出错
.net2.0给对许多类型进行了泛型约束
接口约束
where T : struct 类型必须是一种值类型(struct)
where T : class 类型必须是一种引用类型(class)
where T : new() 类型必须有一个无参数的构造器
where T : class_name 类型可以是class_name或者是它的一个子类
where T : interface_name 类型必须实现指定的接口
可以提供多个约束,用“,”分隔
构造器约束 (现在只支持无参构造器)
class C<T>
where T:new()
{
保证此类只接收一个无参的构造函数
}
值类型,引用类型的约束
public struct A
{
}
public class C<T>
where T : struct
{ }
5.总结
C#泛型能力由CLR在运行时支持,C++在编译时所支持的静态模板,Java在编译层面“搽试法”支持的简单的泛型。
支持的泛型类型:类,结构,接口,委托共四种。
约束方式:采用“基类,接口,构造器,值类型/引用类型”来实现对类型参数的“显示约束”。
好处:
(1)它利用“参数化类型”将类型抽象化,从而实现更为灵活的复用。在原来呢我们声明一个变量的时候就必须马上指出它的类型,泛型允许将变量的类型声明推迟到它使用的时候。
(2)更强的类型安全(Object类型不安全),更好的复用,更高的效率(装箱拆箱),更清楚的约束和使用Object相比,泛型降低了装箱和折箱的负担,减少了类型转换所带来的错误。
泛型编程已经成为一种编程范式,