重温.net2.0新特性之泛型

 

最近在学习.net3.0的相关新特性,在这之前又重温了一下2.0的新特性(之后会写一篇浅谈.net3.0的新特性),温故而知新,这是个永恒不变的学习的发方法,也是因为在第一次接触一个新东西所了解的东西,过了一段时间,再去重新学习,你会更深刻的理解、体会以前懂的,而且还会发现以前不懂的东西。不说废话了,下面是我学习的总结,基本上是按照用法与机制说的:

一.泛型

1.       泛型的由来

原来我们使用的数组是初始化的时候就要指定它的长度,这给程序造成了极大的约束与不便,所以.net引入了ArrayList可变长数组,通过ArrayList实例的Add(Object obj)添加数据元素,这样就可以依据实际情况动态的加减的数组的长度。可是注意一下,Add方法参数接收的是一个Object类型的参数,如果我们初始化这个数组为值类型,如int ,double类型的话添加元素时就要将传进去的元素进行装箱,读取数据的时候还要做拆箱操作,从而耗费了系统资源。此外,接收Object类型参数还存在一个弊端,看下面这段代码:

 1            ArrayList list = new ArrayList();
 2 
 3             list.Add(13);
 4 
 5             list.Add(14);
 6 
 7             // list.Add(15.0);
 8             int total=0;
 9             foreach (int val in list)
10             {
11                 total = total + val;
12             }
13 


 

编译并运行上段代码没有问题,如果把上面注释的那一行的注释去掉,再编译,没问题,运行,就出错了,指定的转换无效,因为第三个元素15.0并不是整型的,在循环的时候就发生了运行时异常,这是我们不想看到的。这就说明ArrayList是类型不安全的。那么能不能有一种办法避免这个异常,能在编译时就给我们报一个错误呢?答案是肯定的.net2.0利用泛型解决了这个问题。它自带了一个泛型类List,我们可以用它的时候指定泛型类的类型。

2.       泛型的用法

泛型是这样定义的:所谓泛型,即通过参数化类型来实现在同一份代码上操作多种数据类型。简单地说泛型什么类型都可以。

这就像上面提到的.net内置泛型List,在用到它的时候指定它的类型,若向其添加的元素不是它实例化时指定的类型,编译器就会报错,不会在运行时发生异常,同时初始化的同时,它的类型已经指定了,在添加和读取数据的时候就不会像ArrayList那样装箱拆箱了,节省了不必要的开销。上面的例子如果用泛型来实现就是这样的。

         List<int> list = new List<int>();
            list.Add(
13);
            list.Add(
14);
           
//list.Add(15.0);
            int total = 0;
            
foreach (int val in list)
            
{
                total 
= total + val;
            }

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在中间只充当占位符。看了下面的一段简单代码和编译程序就会得到证实了,

我定义了一个非常简单的泛型类

class listTest<T>
{
         
public T s;
         
public listTest(T ss)
         
{
                   s
=ss;
         }

}



此泛型类的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相比,泛型降低了装箱和折箱的负担,减少了类型转换所带来的错误。

泛型编程已经成为一种编程范式
posted @ 2008-06-20 17:51  慧致澜馨  阅读(1359)  评论(6编辑  收藏  举报