CSharpThinking---迭代器(三)

 

  本章主要学习迭代器的相关内容,以便对Linq更好的理解。其中涉及两个比较重要的类IEnumerator和IEnumerable,这些类是扩展方法及Linq的基础,具体详情请参考MSDN。

  迭代器是行为模式的一种范例,而行为模式是一种简化对象之间通信的设计模式。它允许你一个数据项列表中的所有元素,而无需关心序列是什么类型---数组,列表,链表等。它能

非常有效的构建出一个数据管道,数据项由此进入管道,并以不同的变换方式来遍历这些数据,或者在访问到末端之前进行一些过滤操作,事实上,这也是Linq核心模式的一种。

 

  一,迭代器重要概念

    1.1 如果一个类实现了IEnumerable接口,则它就可以被迭代器访问,称为可迭代,调用GetEnumerator返回IEnumerator,这就是迭代器本身,相当于数据库的游标。

    1.2 C#1中不能试用yield return 。而C#2中实现了 yield return模式,大大简化构建一个迭代器。

 

  二,迭代器工作流程

    2.1 手动实现yield return,工作流程展示。

 1         static readonly string Padding = new string(' ', 30);
 2 
 3         static IEnumerable<int> CreateEnumerable() 
 4         {
 5             Console.WriteLine("{0} start of CreatEnumerable()",Padding);
 6 
 7             for (int i = 0; i < 3; i++)
 8             {
 9                 Console.WriteLine("{0} About to yield {1}", Padding, i);
10                 yield return i;
11                 Console.WriteLine("{0} After yield return",Padding);
12             }
13             Console.WriteLine("{0} Yielding final value",Padding);
14             yield return -1;
15             Console.WriteLine("{0} End of CreatEnumerable()", Padding);
16         }
17 
18         public void TestMain() 
19         {
20             IEnumerable<int> iterable = CreateEnumerable();
21             IEnumerator<int> iterator = iterable.GetEnumerator();
22 
23             Console.WriteLine("Starting to iterate");
24             while (true)
25             {
26                 Console.WriteLine("Calling MoveNext()...");
27                 bool result = iterator.MoveNext();
28                 Console.WriteLine("...MoveNext result={0}",result);
29                 if (!result) 
30                 {
31                     break;
32                 }
33                 Console.WriteLine("Fetching Current...");
34                 Console.WriteLine("...Current result = {0}",iterator.Current);
35             }
36         }

      结果:

    以上结果说明问题:

      1. 在第一次调用MoveNext之前,CreateEnumerable中的代码不会被调用。

      2. 所有工作都在调用MoveNext的时候就完成了,获取Current值不会执行任何代码;

      3. 在yield return的位置,代码就停止执行,在下一次调用MoveNext的时候又继续执行;

      4. 在一个方法中的不同地方可以书写多个yield return语句;

      5. 代码不会在最后的yield return出结束---相反,而是通过返回false的MoveNext调用来借宿方法的执行。

   

    2.2 与try/finally一起使用的yield break

      2.2.1 使用yield break不会像yield return一样,看上去退出了循环。

      2.2.2 如果使用foreach循环调用迭代器成员,则最后会隐式执行Dispose(),导致最终会执行finally,代码如下:

 1         static IEnumerable<int> CountWithTimeLimit(DateTime dt)
 2         {
 3             try
 4             {
 5                 for (int i = 0; i < 100; i++)
 6                 {
 7                     if (DateTime.Now >= dt)
 8                         yield break;
 9                     yield return i;
10                 }
11             }
12             finally
13             {
14                 Console.WriteLine("Stopping...");
15             }
16         }
17 
18         void TestMain2()
19         {
20             DateTime stop = DateTime.Now.AddSeconds(2);
21             foreach (int i in CountWithTimeLimit(stop))
22             {
23                 Console.WriteLine("Received {0} ", i);
24                 if (i > 3)
25                 {
26                     Console.WriteLine("Returning...");
27                     return;
28                 }
29                 Thread.Sleep(300);
30             }
31         }
32 
33         /* Result:
34          *  Received 1
35          *  Received 2
36          *  Received 3
37          *  Received 4
38          *  Returning...
39          *  Stropping...
40          *  */

 

  三, 迭代器例子

    3.1 C#2 例子

 1         /// <summary>
 2         ///  C#2.0 迭代器应用
 3         /// </summary>
 4         /// <remarks>
 5         /// 封装内部方法
 6         /// </remarks>
 7         /// <param name="provider"></param>
 8         /// <returns></returns>
 9         static IEnumerable<string> ReadLines(Func<TextReader> provider)
10         {
11             using (TextReader tr = provider())
12             {
13                 string line;
14                 while ((line = tr.ReadLine()) != null)
15                 {
16                     yield return line;
17                 }
18             }
19         }
20         /// <summary>
21         /// 重载ReadLines
22         /// </summary>
23         /// <param name="filename"></param>
24         /// <param name="encoding"></param>
25         /// <returns></returns>
26         static IEnumerable<string> ReadLines(string filename, Encoding encoding)
27         {
28             return ReadLines(delegate { return new StreamReader(filename, encoding); });
29         }
30         /// <summary>
31         /// 测试代码
32         /// </summary>
33         void TestMain3()
34         {
35             foreach (string line in ReadLines("text.txt", Encoding.Default))
36             {
37                 Console.WriteLine(line);
38             }
39         }

    3.2 由于迭代器的延迟特性,如果想在调用之前检查代码,如下所示:(上文中的MoveNext之前是不会执行任何代码的

 1         /// <summary>
 2         /// 外部公开方法:先检查,然后调用私有方法执行(延迟执行)方法。
 3         /// </summary>
 4         /// <typeparam name="T"></typeparam>
 5         /// <param name="source"></param>
 6         /// <param name="predicate"></param>
 7         /// <returns></returns>
 8         public static IEnumerable<T> where<T>(IEnumerable<T> source, Predicate<T> predicate)
 9         {
10             if (source == null || predicate == null)
11                 throw new ArgumentNullException();
12             return WhereImpl(source, predicate);
13         }
14 
15         private static IEnumerable<T> WhereImpl<T>(IEnumerable<T> source, Predicate<T> predicate)
16         {
17             foreach (T item in source)
18             {
19                 if (predicate(item))
20                     yield return item;
21             }
22         }

    3.3 CCR实现伪同步代码:核心思想调用迭代器中的yield return

     这种用法的核心思想是状态机:异步开发中两个复杂的问题就是处理状态和在感兴趣的事情发生之前进行有效的暂停。迭代器块使这两个问题得以完美的解决。

 

 

 

 

 

 

 

posted @ 2013-05-30 14:58  史蒂芬King  阅读(487)  评论(0编辑  收藏  举报