• yield语句


        自C#的第一个版本以来,使用foreach语句可以轻松地迭代集合。在C#1.0中,创建枚举器仍需要做大量的工作。C#2.0添加了yield语句,以便于创建枚举器。yield return语句返回集合的一个元素,并移动到下一个元素上。yield break可停止迭代,如下例所示:
    public class HelloCollection
    {
        public IEnumerator<string> GetEnumerator()
        {
            yield return "Hello";
            yield return "World";
        }
    }
        包含yield语句的方法或属性也成为迭代块。迭代块必须声明返回IEnumerator或IEnumerable接口,或者这些接口的泛型版本。这个块可以包含多条yield return语句或yield break语句,但不能包含return语句。
        现在可以用foreach语句迭代集合了:
    static void Main(string[] args)
    {
        HelloCollection collection = new HelloCollection();
        foreach (string s in collection)
            Console.WriteLine(s);
                
    }
        使用迭代块,编译器会生成一个yield类型,其中包含一个状态机,如下面的代码段所示。yield类型实现IEnumerator和IDisposable接口的属性和方法。在下面的例子中,可以把yield类型看做内部类Enumerator。外部类的GetEnumerator方法实例化并返回一个新的yield类型。在yield类型中,变量state定义了迭代的当前位置,每次调用MoveNext时,当前位置都会改变。MoveNext封装了迭代块的代码,并设置了current变量的值,从而使Current属性根据位置返回一个对象。
     1 public class HelloCollection
     2 {
     3     public IEnumerator GetEnumerator()
     4     {
     5         return new Enumerator(0);
     6     }
     7     public class Enumerator : IEnumerator<string>, IEnumerator, IDisposable
     8     {
     9         private int state;
    10         private string current;
    11 
    12         public Enumerator(int state) { this.state = state; }
    13         bool System.Collections.IEnumerator.MoveNext()
    14         {
    15             switch (state)
    16             {
    17                 case 0:
    18                     current = "Hello";
    19                     state = 1;
    20                     return true;
    21                 case 1:
    22                     current = "World";
    23                     state = 2;
    24                     return true;
    25                 case 2:
    26                     break;
    27             }
    28             return false;
    29         }
    30         void System.Collections.IEnumerator.Reset()
    31         {
    32             throw new NotSupportedException();
    33         }
    34         string System.Collections.Generic.IEnumerator<string>.Current
    35         {
    36             get { return current; }
    37         }
    38         object System.Collections.IEnumerator.Current
    39         {
    40             get { return current; }
    41         }
    42         void IDisposable.Dispose() { }
    43     }
    44 }
        yield return语句会生成一个枚举器,而不仅仅生成一个包含的项的列表。这个枚举器通过foreach语句调用。从foreach中依次访问每一项时,就会访问枚举器。这样就可以迭代大量的数据,而无需一次把所有的数据都读入内存。
    迭代集合的不同方式
        在下面这个比Hello World示例略大但比较真实的示例中,可以使用yield return语句,以不同方式迭代集合的类。类MusicTitles 可以用默认方式通过GetEnumerator方法迭代标题,用Reverse方法逆序迭代标题,用Subset方法迭代子集:
     1 public class MusicTitles
     2 {
     3     string[] names = { "Tubular Bells", "Hergest Ridge", "Ommadawn", "Platinum" };
     4 
     5     public IEnumerator<string> GetEnumerator()
     6     {
     7         for (int i = 0; i < 4; i++)
     8             yield return names[i];
     9     }
    10     public IEnumerable<string> Reverse()
    11     {
    12         for (int i = 3; i >= 0; i--)
    13             yield return names[i];
    14     }
    15     public IEnumerable<string> Subset(int index, int length)
    16     {
    17         for (int i = index; i < index + length; i++)
    18             yield return names[i];
    19     }
    20 }
        类支持的默认迭代时定义为返回IEnumerator的GetEnumerator方法。命名的迭代返回IEnumerable。
    用yield return返回枚举器
        使用yield return语句还可以完成更复杂的任务,例如,从yield return中返回枚举器。在TicTacToe游戏中有9个域,玩家轮流在这些域中放置一个“十”字或一个圆。这些操作有GameMoves类模拟。方法Cross和Circle是创建迭代器类型的迭代块。变量cross和circle在GameMoves类的构造函数中设置为Cross何Circle方法。这些字段不设置为调用方法,而是设置为用迭代块定义的迭代类型。在Cross迭代块中,将移动操作的信息写到控制台上,并递增移动次数。如果移动次数大于8,就用yield break停止迭代,否则,就在每次迭代中返回yield类型circle枚举变量。Circle迭代块非常类似于Cross迭代块,只是它在每次迭代中返回cross迭代器类型。
     1 public class GameMoves
     2     {
     3         private IEnumerator cross;
     4         private IEnumerator circle;
     5 
     6         public GameMoves()
     7         {
     8             cross = Cross();
     9             circle = Circle();
    10         }
    11         private int move = 0;
    12         const int MaxMoves = 9;
    13 
    14         public IEnumerator Cross()
    15         {
    16             while (true)
    17             {
    18                 Console.WriteLine("Cross, move {0}", move);
    19                 if (++move >= MaxMoves)
    20                     yield break;
    21                 yield return circle;
    22             }
    23         }
    24         public IEnumerator Circle()
    25         {
    26             while (true)
    27             {
    28                 Console.WriteLine("Circle, move {0}", move);
    29                 if (++move >= MaxMoves) yield break;
    30                 yield return cross;
    31             }
    32         }
    33     }
        在客户端程序中,可以使用如下方式使用GameMoves类。将枚举器设置为由game.Cross()返回的枚举器类型,以设置第一次调用。在while循环中,调用enumerator.MoveNext()。第一次调用enumerator.MoveNext()时,会调用Cross方法,Cross方法使用yield语句返回另一个枚举器。返回值可以用Current属性访问,并设置enumerator变量,用于下一次循环:
    var game = new GameMoves();
    IEnumerator enumerator = game.Cross();
    while (enumerator.MoveNext())
        enumerator = enumerator.Current as IEnumerator;
    程序的输出会显示交替移动的情况,直到最后一次移动:
    Cross, move 0
    Circle, move 1
    Cross, move 2
    Circle, move 3
    Cross, move 4
    Circle, move 5
    Cross, move 6
    Circle, move 7
    Cross, move 8
  • 相关阅读:
    BZOJ2208 [Jsoi2010]连通数[缩点/Floyd传递闭包+bitset优化]
    loj515 「LibreOJ β Round #2」贪心只能过样例[bitset+bool背包]
    BZOJ3331 [BeiJing2013]压力[圆方树+树上差分]
    BZOJ4010 [HNOI2015]菜肴制作[拓扑排序+贪心]
    BZOJ2140 稳定婚姻[强连通分量]
    hdu4612 Warm up[边双连通分量缩点+树的直径]
    BZOJ2730 [HNOI2012]矿场搭建[点双连通分量]
    BZOJ3887 [Usaco2015 Jan]Grass Cownoisseur[缩点]
    BZOJ1016 [JSOI2008]最小生成树计数[最小生成树+搜索]
    hdu4786 Fibonacci Tree[最小生成树]【结论题】
  • 原文地址:https://www.cnblogs.com/ChrisLi/p/4191054.html
Copyright © 2020-2023  润新知