• Like
  • Save
Deferred execution
Upcoming SlideShare
Loading in...5
×
 

Deferred execution

on

  • 1,603 views

Deferred execution

Deferred execution

Statistics

Views

Total Views
1,603
Views on SlideShare
965
Embed Views
638

Actions

Likes
0
Downloads
9
Comments
0

9 Embeds 638

http://www.cnblogs.com 612
http://rritw.com 15
http://www.cofftech.com 2
http://renren.it 2
http://www.haogongju.net 2
http://webcache.googleusercontent.com 2
http://archive.cnblogs.com 1
http://www.stackdoc.com 1
http://www.renren.it 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Deferred execution Deferred execution Presentation Transcript

    • 浅析延迟执行
      实现,优点,陷阱以及题外话
    • 背景
      假设一个场景: 我们需要获取并遍历一个包含大量元素的序列,从中找出我们需要的某个元素。
      在此为了简单起见,我们假设该序列包含1000万个int值,我们需要找到的是100万这个值。
    • 第一类实现方式:使用已有类型
      要从一个序列中过滤出一个值则首先需要生成这个序列。在此我们随意挑选三个序列类型:
      int[]
      Collection<int>
      List<int>
    • 第二种方式:延迟执行
      要实现延迟执行(Deferred Execution)有两种方式可选:
      自己创建实现了IEnumerable<T>接口的类型
      使用yield关键字
      这两种方式具体是如何实现延迟执行的?我们稍后根据代码讲解。
    • 实现(1)
      第一种方式中的三个已有类型的实现都相当简单,请看代码。
      生成数组的代码:
      生成Collection<int>的代码:
    • 实现(1)
      生成List<int>的代码:
    • 实现(2)
      自己创建实现了IEnumerable<T>的类型的方式是最复杂,最难写的。这个方式的基本思路就是构造一个状态机,由于要在多个状态之间做切换,所以很容易出错。请看代码:
      由于代码过长,在此只给出代码结构图。这个类型同时实现了IEnumerable<T>和IEnumerator<T>两个接口。其MoveNext方法和Current属性每被调用一次时,才即时生成一个元素,这样就避免了一次性填充整个序列,从而实现了延迟执行。
    • 实现(3)
      使用yield关键字是最简单,最偷懒的方式。
      实际上,yield背后对应的实现和我们讲到的上一种方式基本是一样的。编译器会把包含yield的代码块构造成一个同时实现了IEnumerable<T>和IEnumerator<T>的类型。
    • 测试(1)
      测试中有几个辅助方法(TestTime,IterateSequence和TestSpeed)需要简单说明,请看代码:
      TestTime的代码:
      TestTime接受一个Action类型的参数,在方法体内执行action并为其计时,最后输出所耗时间。
    • 测试(1)
      IterateSequence的代码:
      这段代码很简单:迭代一个序列,当找到要找的值之后则break出去。
    • 测试(1)
      TestSpeed的代码:
      这段代码测试所有五种实现方式的效率。把创建序列和过滤序列的代码包裹在lambda表达式中传入TestTime。
    • 测试(2)
      从1000万个元素中筛选
      从1亿个元素中筛选
      可以发现,当序列中元素数量增加时,前三种实现方式的耗时量都在呈线性增长。
      而后两种实现方式的耗时量则基本没有变化。
    • 总结(1):延迟执行的好处
      从前面的测试结果中可以看出,延迟执行的最明显的优势即在于不会立即创建整个序列,而是在调用方索取时才即时生成元素。
      这也正好解释了为什么将序列容量从1000万增加为1亿时延迟执行的方式执行时间基本不变。因为延迟执行的方法总是只生成100万个元素而已。
    • 总结(2):可能的陷阱
      由于用来生成序列的算法被封装在了状态机内,所以每次用foreach迭代这个序列时,整个序列都会被重新生成一次。
      如果需要避免这种行为,可以通过在延迟执行的返回结果上调用ToArray()或ToList()。然后在每次迭代中都使用已经填充好的Array或List。
      其实这种特性在有的场景下是很有益的,比如生成序列的算法依赖于某些外部的变化条件(数据库,网络数据或者系统时间)。
    • 题外话(1):foreach
      我们每天都会用到的foreach究竟是如何实现的呢?
      可以看出,一个foreach的“空转”循环基本等价于一个while循环加一个try/finally代码块。
      请注意在finally代码块中调用了Dispose方法。如果这个foreach作用于一个延迟执行方法的返回值上,那么对Dispose的调用就相当于把状态机的状态清零。
    • 题外话(2):序列的重新生成
      前面讲foreach的题外话其实是为了讲解序列的重新生成做基础。
      前面已经讲过,foreach的尾部会调用迭代器的Dispose方法,把状态机的状态清零。这样,如果有下一个foreach来迭代同一个序列的话,则会将封装在状态机内的生成元素的算法重新执行一遍,也就相当于重新生成了整个序列。
      这样说或许过于晦涩,请看下一页的图解。
    • 题外话(2):序列的重新生成
      请看这把春田步枪,你装入子弹(调用GetEnumerator),撞针顶住了第一颗子弹(第一次调用MoveNext),开枪(访问Current属性),然后撞针顶住下一颗子弹(又一次调用MoveNext),反复开枪(反复调用MoveNext并访问Current属性),直到子弹耗尽(MoveNext返回了false),枪膛打开了(调用了Dispose)。然后再装入子弹开始下一轮的射击(序列的重新生成)。
    • 谢谢