• 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参数,每個迭代器有它自己的一份拷贝。

  • 相关阅读:
    cocos2dx-lua UI编辑器的设计思路
    软件中Undo(撤回)和Redo(重做)的实现
    Cocos2d-x上适合做工具的UI库:ImGui
    静态成员
    命名空间
    类对象的初始化
    函数的重载、重写与隐藏
    类的继承关系与访问限定符
    多继承
    友元函数与友元类
  • 原文地址:https://www.cnblogs.com/loushangshaonian/p/2546489.html
Copyright © 2020-2023  润新知