yield 迭代器 转 一篇

这个系列的主要目的是尽量能覆盖C# 1.1之后的语法更新,以便让大家能够熟悉C# 2.0到4.0的语法特性,以提高编程效率,这里我忽略了一些诸如泛型、LINQ等等需要大章节才能阐述清楚的东西,原因是关注这些知识点的文章比比皆是,我所要写的语法都是一些比较小的,容易被人所忽略的地方。如果我有任何遗漏或者错误的地方,请给我个站内消息


1. 迭代器 适用范围:c# 2.0之后、

C# 1.0开始提供了非常方便的foreach循环,只要一个数据集实现了一个无参数的,返回Enumerator的GetEnumerator方法(甚至不需要实现任何接口),就可以在foreach循环中遍历数据集中的每个元素。大部分情况下,我们将数据存入某个Collection类,利用Collection类的GetEnumerator方法,就可以很方便地使用Foreach循环。

但有些时候我们必须自己去实现Enumerator,比如说打印某年每个月的天数,为了体现OOP的原则,我们的程序逻辑(即对闰年的判断,很高兴C#提供了相关的方法)应该被封装起来。

Java代码 复制代码 收藏代码
  1. using System;
  2. using System.Collections;
  3. namespace SharpDemo{
  4. publicclass DaysOfTheMonth
  5. {
  6. public DaysOfTheMonth(int year)
  7. {
  8. this.year = year;
  9. }
  10. int year = 1900;
  11. public System.Collections.IEnumerator GetEnumerator()
  12. {
  13. returnnew DaysOfMonthEnumrator(this.year);
  14. }
  15. class DaysOfMonthEnumrator : IEnumerator
  16. {
  17. privateint[] days = newint[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
  18. public DaysOfMonthEnumrator(int year)
  19. {
  20. if (DateTime.IsLeapYear(year))
  21. {
  22. days[1] = 29;
  23. }
  24. }
  25. privateint index = -1;
  26. public IEnumerator GetEnumerator()
  27. {
  28. returnthis;
  29. }
  30. publicvoid Reset()
  31. {
  32. index = -1;
  33. }
  34. public object Current
  35. {
  36. get
  37. {
  38. if (this.index < this.days.Length)
  39. {
  40. returnthis.days[this.index];
  41. }
  42. else
  43. {
  44. thrownew IndexOutOfRangeException();
  45. }
  46. }
  47. }
  48. public bool MoveNext()
  49. {
  50. if (this.days.Length == 0)
  51. {
  52. returnfalse;
  53. }
  54. else
  55. {
  56. this.index += 1;
  57. if (this.index == this.days.Length)
  58. {
  59. returnfalse;
  60. }
  61. else
  62. {
  63. returntrue;
  64. }
  65. }
  66. }
  67. }
  68. }
  69. }
using System;
using System.Collections;
namespace SharpDemo{

    public class DaysOfTheMonth
    {

        public DaysOfTheMonth(int year)
        {
            this.year = year;
        }      

       int year = 1900;

        public System.Collections.IEnumerator GetEnumerator()
        {
            return new DaysOfMonthEnumrator(this.year);
        }

        class DaysOfMonthEnumrator : IEnumerator
        {
            private int[] days = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

            public DaysOfMonthEnumrator(int year)
            {
                if (DateTime.IsLeapYear(year))
                {
                    days[1] = 29;
                }

            }
            private int index = -1;

            public IEnumerator GetEnumerator()
            {
                return this;
            }

            public void Reset()
            {
                index = -1;
            }

            public object Current
            {
                get
                {
                    if (this.index < this.days.Length)
                    {
                        return this.days[this.index];
                    }
                    else
                    {
                        throw new IndexOutOfRangeException();
                    }
                }
            }

            public bool MoveNext()
            {
                if (this.days.Length == 0)
                {
                    return false;
                }
                else
                {
                    this.index += 1;
                    if (this.index == this.days.Length)
                    {
                        return false;
                    }
                    else
                    {
                        return true;
                    }
                }
            }

    }

  
    }
}



这段代码较长但不难理解,它相当好地封装了程序逻辑,调用起来也相当简洁优雅:

Java代码
  1. DaysOfTheMonth enu = new DaysOfTheMonth(1981);
  2. foreach( int days in enu)
  3. Console.Out.WriteLine(days);
 DaysOfTheMonth enu = new DaysOfTheMonth(1981);            
 foreach( int days in enu)
       Console.Out.WriteLine(days);



我们也看到实现Enumerator的过程未免过于复杂,一旦我们需要多个Enumerator来进行逆序,正序,奇偶数序迭代,代码就会相当繁杂。

C# 2.0之后新的yield关键字使得这个过程变得轻松简单。

Java代码
  1. publicclass DaysOfMonth
  2. {
  3. int year = 1900;
  4. int[] days = newint[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
  5. public DaysOfMonth2(int year)
  6. {
  7. this.year = year;
  8. if (DateTime.IsLeapYear(year))
  9. {
  10. days[1] = 29;
  11. }
  12. }
  13. public IEnumerator GetEnumerator()
  14. {
  15. for (int i = 0; i < this.days.Length; i++)
  16. {
  17. yield returnthis.days[i];
  18. }
  19. }
  20. }
    public class DaysOfMonth
    {
        int year = 1900;
        int[] days = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
        public DaysOfMonth2(int year)
        {
            this.year = year;
            if (DateTime.IsLeapYear(year))
            {
                days[1] = 29;
            }
        }

        public IEnumerator GetEnumerator()
        {
            for (int i = 0; i < this.days.Length; i++)
            {
                yield return this.days[i];
            }
        }

    }



这就是C# 2.0 中的迭代器,它使您能够方便地在类或结构中支持 foreach 迭代,而不必实现整个 IEnumerable 接口。

迭代器的返回类型必须为 IEnumerable、IEnumerator、IEnumerable<T> 或 IEnumerator<T>。当编译器检测到迭代器时,它将自动生成 IEnumerable 或 IEnumerable<T> 接口的 Current、MoveNext 和 Dispose 方法。

yield 关键字用于指定返回的值。到达 yield return 语句时,会保存当前位置。下次调用迭代器时将从此位置重新开始执行。而yield break则可以中止迭代,下面的代码有助于理解这点。

Java代码
  1. publicclass CityCollection : IEnumerable<string>
  2. {
  3. public IEnumerator<string> GetEnumerator()
  4. {
  5. yield return"New York";
  6. yield return"Paris";
  7. yield return"London";
  8. }
  9. }
public class CityCollection : IEnumerable<string>
{
   public IEnumerator<string> GetEnumerator()
   {
      yield return "New York";
      yield return "Paris";
      yield return "London";
   }
}



会依次打印出"New York", "Paris" "London". 有时候迭代器与迭代一起可以产生奇妙的效果,但也会耗费大量的内存。

Java代码
  1. IEnumerable<T> ScanInOrder(Node<T> root)
  2. {
  3. if(root.LeftNode != null)
  4. {
  5. foreach(T item in ScanInOrder(root.LeftNode))
  6. {
  7. yield return item;
  8. }
  9. }
  10. yield return root.Item;
  11. if(root.RightNode != null)
  12. {
  13. foreach(T item in ScanInOrder(root.RightNode))
  14. {
  15. yield return item;
  16. }
  17. }
  18. }
   IEnumerable<T> ScanInOrder(Node<T> root)
   {
      if(root.LeftNode != null)
      {
         foreach(T item in ScanInOrder(root.LeftNode))
         {
            yield return item;
         }
      }

      yield return root.Item;
 
      if(root.RightNode != null)
      {
         foreach(T item in ScanInOrder(root.RightNode))
         {
            yield return item;
         }
      }
   }




foreach语句会隐式地调用集合的无参的GetEnumerator方法來得到一個迭代器。一个集合类中只能定义一个这样的无参的GetEnumerator方法,不过我们也可以在类中实现多个迭代器,但每个迭代器都必须像任何类成员一样有唯一的名称。

Java代码
  1. using System.Collections.Generic;
  2. publicclass Stack<T>: IEnumerable<T>
  3. {
  4. T[] items;
  5. int count;
  6. publicvoid Push(T data) {...}
  7. public T Pop() {...}
  8. public IEnumerator<T> GetEnumerator() {
  9. for (int i = count – 1; i >= 0; --i) {
  10. yield return items[i];
  11. }
  12. }
  13. public IEnumerable<T> TopToBottom {
  14. get {
  15. returnthis;
  16. }
  17. }
  18. public IEnumerable<T> BottomToTop {
  19. get {
  20. for (int i = 0; i < count; i++) {
  21. yield return items[i];
  22. }
  23. }
  24. }
  25. }
using System.Collections.Generic;
public class Stack<T>: IEnumerable<T>
{
         T[] items;
         int count;
         public void Push(T data) {...}
         public T Pop() {...}
         public IEnumerator<T> GetEnumerator() {
                  for (int i = count – 1; i >= 0; --i) {
                           yield return items[i];
                  }
         }
         public IEnumerable<T> TopToBottom {
                  get {
                           return this;
                  }
         }
         public IEnumerable<T> BottomToTop {
                  get {
                           for (int i = 0; i < count; i++) {
                                    yield return items[i];
                           }
                  }
         }
}



TopToBottom属性的get访问器只返回this,因为Stack本身就是一个可枚举类型。BottomToTop屬性使用C#迭代器返回了另一个可枚举接口。

实现IEnumerable接口的类看起来非常象一个枚举器工厂类,每次都产生一个独立的IEnumerator类。

Java代码
  1. using System;
  2. using System.Collections.Generic;
  3. class Test
  4. {
  5. static IEnumerable<int> FromTo(int from, int to) {
  6. while (from <= to) yield return from++;
  7. }
  8. staticvoid Main() {
  9. IEnumerable<int> e = FromTo(1, 10);
  10. foreach (int x in e) {
  11. foreach (int y in e) {
  12. Console.Write("{0,3} ", x * y);
  13. }
  14. Console.WriteLine();
  15. }
  16. }
  17. }
using System;
using System.Collections.Generic;
class Test
{
         static IEnumerable<int> FromTo(int from, int to) {
                  while (from <= to) yield return from++;
         }
         static void Main() {
                  IEnumerable<int> e = FromTo(1, 10);
                  foreach (int x in e) {
                           foreach (int y in e) {
                                    Console.Write("{0,3} ", x * y);
                           }
                           Console.WriteLine();
                  }
         }
}



上面的代码打印了一个从1到10的乘法表。注意FromTo方法只被调用了一次用来产生实现了IEnumerable接口的变量e。而e.GetEnumerator()被调用了多次(通过foreach语句)來产生多个相同的迭代器。這些迭代器都封裝了FromTo声明中指定的代碼。注意,迭代其代码的时候改变了from参数。但是,迭代器是独立的,因此对于from参数和to参数,每個迭代器有它自己的一份拷贝。

posted @ 2012-06-12 16:21  楼上少年  阅读(230)  评论(0编辑  收藏  举报