泛型编程入门1

泛型编程1(Generic Programming

.NET Framework 2.0中,引入了泛型的新特性。使用泛型,可以在一定程度上减少编程时的代码量,同时提高运行时安全以及代码执行速度。另外,在.Net Framework2.0类库中,同时也为开发人员提供了泛型的数据结构,以便减轻在1.1版本中的System.Collections命名空间中的数据结构在大量控制值类型数据时,所出现的装箱¥拆箱和运行时安全问题。

5.1 泛型编程的引出

5.1.1 速度问题

.Net1.1,开发人员为了解决类型通用问题,往往会将一些操作(比如:方法)的参数设置为Object类型。这样作虽然可以实现代码的能用,但同时候也带来新的问题,即:运行速度。

之所以使用Object类作为通用操作的参数,是因为事先在开发这些程序的时候,编码人员是无法确定使用者到底会给这些参数传递一个什么类型的值。最典型的例子就是ListBox控件。ListBox中,使用Items属性来实现对列表中的数据进行添加以及索引等操作。但是,微软为广大开发人员编写这个控件的时候,是不可能确定使用者到底要将什么内容添加列表框中的,为此,Items属性了使用ObjectCollection类型,这样,加入到列表中的数据只要是Object类型的派生类即可。下面代码是MSDN中的说明:

[ListBindableAttribute(false)]

public class ObjectCollection : IList,

ICollection, IEnumerable

Add方法中的参数,如下面代码所示:

public int Add(

              Object item

)

大家可能会问题,这样的操作会有什么问题吗?如果,添加到列表框中的数据中值类型,那么,就会存在一些问题了,参考下面代码:

for (int iLength = 0; iLength < 100; iLength++)

{

this.listBox1.Items.Add(iLength );

}

//其他操作

for (int iLength = 0; iLength < 100; iLength++)

{

  int b = (int)this.listBox1.Items[iLength ];

}

上面的代码中,我们可以看到,首先在ListBox中添加了100个整型数,然后,经过一系列操作之后,又将ListBox中的数据取出来。因为,在添加数据的时候就是以Object类型添加的,在取的时候自然也是Object类型,所以,每一次取值都要经过一次显示的类型转换。

这里,问题就在于添加到ListBox中的数据是值类型的数据,而ListBox.Add方法中的参数又是Object类型的数据,换句话说,上面的操作实际是将一个值类型的数据传递给了一个Object类型的变量,这一点使用者都会知道,是一个装箱(Boxing)过程,而将数据取出来,恰好又是一个拆箱(Unboxing)过程。

实际上,一次装箱及拆箱(要说明的是:理论上来讲装箱拆箱不是一个完全互逆的过程)至少要在内存里转移两次,下图是过程的说明:

首先,由于值类型是存储在栈区(Stack),而Object类型存储在堆区(Heap)。因此,直观上来讲,数据至少要经历:Stack->Heap->Stack的一个过程,这样内存就已经转移了两次。

另外,我们会将.NET中的堆区称为托管堆(Managed Heap,意思就是说,放在堆区的数据会受到CLR的管理,为了保证堆区内容的整洁并且方便GC进行垃圾回收,被放置在堆区的数据会被CLR来回的转移。实际上,在上面图中的对象obj还会经历多次的内存转移。因此,我们说,一次装箱及拆箱,在内存里至少会转移两次

但实际上,对于ListBox这样的表现层控件来讲,无论经过多少次的内存转移,运行速度都不会受到太大的影响,毕竟,一个界面上的控件中不会存在太多的数据项。但是,对于后台的代码来讲,过于频繁的装箱以及拆箱,就会产生比较大的影响,特别是数据量比较大的时候。下面代码就为大家展示了,当数据类型已知的时候,以及数据类型未知并通过装箱和拆箱来操作相同规模的数据时,操作时间的对比:

案例操作020501 数据类型已知以及数据类型未知通过装箱和拆箱来操作相同规模的数据时,操作时间的对比

参照如下两段代码:

l         代码片段一:数据类型已知(整型)

int b = 0;

DateTime start = DateTime.Now;

while (b<10000000)

{

       b = b + 1;

}

TimeSpan span = DateTime.Now - start;

this.listBox1.Items.Add(类型已知:+span .TotalMilliseconds);

l         代码片段二:数据类型未知

int b = 0;

object obj = b;

DateTime start = DateTime.Now;

while ((int)obj < 10000000)

{

    obj  = (int)obj + 1;

}

TimeSpan span = DateTime.Now - start;

this.listBox1.Items.Add(类型未知:+span.TotalMilliseconds);

 

通过上面两组代码进行对比,读者可以看到,在相同级别(10000000)的循环下,第二组代码是通过装箱及拆来实现的,速度到底会差多少呢?请参考下图:

在实际应用中,开发人员会常常使用到System.Collections命名空间中的集合类(数据结构类)。下表是常用的几个数据结构类:

类名

加入数据结构的方法

从数据结构取值的方法

ArrayList(链表)

Add(object  item)

Object  [ ]

Queue(队列)

DeQueue(object item)

Object  DeQueue()

Stack()

Push(object item)

Object  Pop()

HashTable(哈希表)

Add(string key,object value)

Object  [string key ]

从上表中,我们可以观察出,由于微软在作这些数据结构类的时候,并不知道使用者会将什么类型的数据放置到里面,因此,无论是进入数据结构,还是从数据结构中取出,都是利用Object类型来实现的。如果所操作的数据是值类型,那么速度就是一个值得我们考虑的问题了。

5.1.2 安全性问题

首先,来看如下代码,该段代码中就存在一些运行时安全问题:

案例操作020502:通过Object类型传递参数时的安全问题

private void button1_Click(object sender, EventArgs e)

{

    ArrayList fruitList = new ArrayList();

    for (int iFruit = 0; iFruit < 50; iFruit++)

    {

         Apple app = new Apple();

         fruitList.Add(app);

    }

    for (int iFruit = 0; iFruit < 50; iFruit++)

    {

          Orange org = new Orange();

          fruitList.Add(org );

      }

      //逻辑错误,语法没有错误!

      for (int i = 0; i < 51; i++)

      {

          Apple app=(Apple )fruitList [i];

      }

  }

  class Apple

  {}

  class Orange

  {}

上面代码有什么问题呢?我们先将代码的执行过程进行简单的说明:

1. 建立链fruitList

2. fruitList中添加50Apple类的实例

3. fruitList中添加50Orange类的实例

4. 通过循环取出前51个元素,并认为这些元素都是苹果(这个操作对一个经验丰富的程序员来讲,应该很可笑的,但是如果你今天已经编写了10000行代码,已经晕了呢?)

5. 将代码进行编译

虽然上面代码中的第4步在逻辑上已经出现错误(因为这个循环共有51次,而fruitList中的第50,即第51个元素不是Apple而是一个Orange),但是,整个代码确可以通过编译,关系问题就存在于下面的代码中:

Apple app=(Apple )fruitList [i];

fruitList是一个链表,所以通过索引方式取出的值是Object类型的,因此,在理论上来讲,可以将其转化为任意的派生类型,所以语法上是正确的!如果运行这段代码,当i的值为50的时候,系统就会出现如下异常提示:

我相信,没有任何一个客户喜欢看到这种界面的!

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值