分类

链接

2011 年 12 月
 1234
567891011
12131415161718
19202122232425
262728293031  

近期文章

热门标签

新人福利,免费薅羊毛

现在位置:    首页 > .NET > 正文
共享办公室出租
.NET中yield语句
.NET 暂无评论 阅读(2,734)

 

C# 1.0使用foreach语句可以轻松地迭代集合。在C# 1.0中,创建枚举器仍需要做大量的工作。C# 2.0添加了yield语句,以便于创建枚举器。

yield return语句返回集合的一个元素,并移动到下一个元素上。yield break可停止迭代。

下面的例子是用yield return语句实现一个简单集合的代码。类HelloCollection包含GetEnumerator()方法。该方法的实现代码包含两个yield return语句,它们分别返回字符串Hello和World。

  1. using System;
  2.  
  3. using System.Collection;
  4.  
  5. namespace Wrox.ProCAharp.Arrays
  6.  
  7. {
  8.  
  9. public class HelloCollection
  10.  
  11. {
  12.  
  13. public IEumerator GetEumerator()
  14.  
  15. {
  16.  
  17. yield return "Hello";
  18.  
  19. yield return "World";
  20.  
  21. }
  22.  
  23. }
  24.  
  25. }
  26.  
  27.  

包含 yield语句的方法或属性也称为迭代块。迭代块必须声明为返回 IEnumerator或

IEnumerable接口。这个块可以包含多个yield return语句或yield break语句,但不能包含

return语句。

现在可以用foreach语句迭代集合了:

  1. public class Program
  2.  
  3. {
  4.  
  5. HelloCollection helloCollection = new HelloCollection();
  6.  
  7. foreach (string s in helloCollection)
  8.  
  9. {
  10.  
  11. Console.WriteLine(s);
  12.  
  13. }
  14.  
  15. }
  16.  
  17. }

使用迭代块,编译器会生成一个 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. Enumerator enumerator = new Enumerator();
  6. return enumerator;
  7. }
  8. public class Enumerator : IEnumerator, IDisposable
  9. {
  10. private int state;
  11. private object current;
  12. public Enumerator(int state)
  13. {
  14. this.state = state;
  15. }
  16. bool System.Collections.IEnumerator.MoveNext()
  17. {
  18. switch (state){
  19. case 0:
  20. current = "Hello";
  21. state = 1; 
  22. return true;
  23. case 1:
  24. current = "World";
  25. state = 2;
  26. return true;
  27. case 2:
  28. break;
  29. }
  30. return false;
  31. }
  32. void System.Collections.IEnumerator.Reset()
  33. {
  34. throw new NotSupportedException();
  35. }
  36. object System.Collections.IEnumerator.Current
  37. {
  38. get
  39. {
  40. return current;
  41. }
  42. }
  43. void IDisposable.Dispose()
  44. {
  45. }
  46. }
  47. }

现在使用yield return语句,很容易实现允许以不同方式迭代集合的类。类MusicTitles可以用默认方式通过 GetEnumerator()方法迭代标题,用 Reverse()方法逆序迭代标题,用Subset()方法搜索子集:

  1. public class MusicTitles
  2.  
  3. {
  4.  
  5. string[] names = {
  6.  
  7. "Tubular Bells", "Hergest Ridge",
  8.  
  9. "Ommadawn", "Platinum");
  10.  
  11. public IEnumerator GetEnumerator()
  12.  
  13. {
  14.  
  15. for (int i = 0; i < 4; i++)
  16.  
  17. {
  18.  
  19. yield return names[i];
  20.  
  21. }
  22.  
  23. }
  24.  
  25. public IEnumerable Reverse()
  26.  
  27. {
  28.  
  29. for (int i = 3; i >= 0; i–)
  30.  
  31. {
  32.  
  33. yield return names[i];
  34.  
  35. }
  36.  
  37. }
  38.  
  39. public IEnumerable Subset( int index, int length)
  40.  
  41. {
  42.  
  43. for (int i = index; i < index + length; i++)
  44.  
  45. {
  46.  
  47. yield return names[i];
  48.  
  49. }
  50.  
  51. }
  52.  
  53. }

迭代字符串数组的客户代码先使用GetEnumerator()方法,该方法不必在代码中编写,

因为这是默认使用的方法。然后逆序迭代标题,最后将索引和要迭代的元素个数传送给

Subset()方法,来迭代子集:

  1. MusicTitles titles = new MusicTitles();
  2.  
  3. foreach(string title in titles)
  4.  
  5. {
  6.  
  7. ConsoleWriteLine(title);
  8.  
  9. }
  10.  
  11. ConsoleWriteLine();
  12.  
  13. ConsoleWriteLine("reverse");
  14.  
  15. foreach(string title in titles.Reverse())
  16.  
  17. {
  18.  
  19. ConsoleWriteLine(title);
  20.  
  21. }
  22.  
  23. ConsoleWriteLine();
  24.  
  25. ConsoleWriteLine("subset");
  26.  
  27. foreach(string title in titles.Subset(2, 2))
  28.  
  29. {
  30.  
  31. ConsoleWriteLine(title);
  32.  
  33. }

使用yield语句还可以完成更复杂的任务,例如从yield return中返回枚举器。

在TicTacToe游戏中有9个域,玩家轮流在这些域中放置“十”字或一个圆。这些移

动操作由GameMoves类模拟。方法Cross()和Circle()是创建迭代类型的迭代块。变量cross

和circle在GameMoves类的构造函数中设置为Cross()和Circle()方法。这些域不设置为调

用的方法,而是设置为用迭代块定义的迭代类型。在Cross()迭代块中,将移动操作的信息

写到控制台上,并递增移动次数。如果移动次数大于9,就用yield break停止迭代;否则,

就在每次迭代中返回yield类型cross的枚举对象。Circle()迭代块非常类似于Cross()迭代块,

只是它在每次迭代中返回circle迭代类型。

  1. public calss GameMoves
  2.  
  3. {
  4.  
  5. private IEnumerator cross;
  6.  
  7.  
  8.  
  9. private IEnumerator circle;
  10.  
  11. public GameMoves()
  12.  
  13. {
  14.  
  15. cross = Cross();
  16.  
  17. circle = Circle();
  18.  
  19. }
  20.  
  21. private int move = 0;
  22.  
  23. public IEnumerator Cross()
  24.  
  25. {
  26.  
  27. while (true)
  28.  
  29. {
  30.  
  31. Console.WriteLine("Cross, move {0}", move);
  32.  
  33. move++;
  34.  
  35. if (move > 9)
  36.  
  37. yield break;
  38.  
  39. yield return circle;
  40.  
  41. }
  42.  
  43. }
  44.  
  45. public IEnumerator Circle()
  46.  
  47. {
  48.  
  49. while (true)
  50.  
  51. {
  52.  
  53. Console.WriteLine("Circle, move {0}", move);
  54.  
  55. move++;
  56.  
  57. if (move > 9)
  58.  
  59. yield break;
  60.  
  61. yield return cross;
  62.  
  63. }
  64.  
  65. }
  66.  
  67. }

在客户程序中,可以以如下方式使用GameMoves类。将枚举器设置为由game.Cross()返回的枚举器类型,以设置第一次移动。enumerator.MoveNext调用以迭代块定义的一次迭代,返回另一个枚举器。返回的值可以用Current属性访问,并设置为enumerator变量,用于下一次循环:

  1. GameMoves game = new GameMoves();
  2.  
  3. IEnumerator enumerator = game.Cross();
  4.  
  5. while (enumerator.MoveNext())
  6.  
  7. {
  8.  
  9. enumerator = (IEnumerator) enumerator.Current;
  10.  
  11. }

这个程序的输出会显示交替移动的情况,直到最后一次移动:

  1. Cross, move 0
  2.  
  3. Circle, move 1
  4.  
  5. Cross, move 2
  6.  
  7. Circle, move 3
  8.  
  9. Cross, move 4
  10.  
  11. Circle, move 5
  12.  
  13. Cross, move 6
  14.  
  15. Circle, move 7
  16.  
  17. Cross, move 8

5.7 小结

本章介绍了创建和使用简单数组、多维数组和锯齿数组的C#记号。Array类在C#数组

的后台使用,这样就可以用数组变量调用这个类的属性和方法。

我们还探讨了如何使用IComparable和IComparer接口给数组中的元素排序,描述了

用Array类执行的IEnumerable、ICollection和IList接口的特性,最后论述了C# 2.0中新

增的yield语句的优点。

数组和相关主题的更多信息,可参阅如下章节:第6章介绍了运算符和强制类型转换,

其中探讨了定制索引器的创建。第7章讨论了委托和事件。Array类的一些方法将委托用

作参数。第10章介绍了本章探讨的集合类。集合类有较灵活的尺寸,第10章还介绍了其

他容器,如字典和链表。

============ 欢迎各位老板打赏~ ===========

本文版权归Bruce's Blog所有,转载引用请完整注明以下信息:
本文作者:Bruce
本文地址:.NET中yield语句 | Bruce's Blog

发表评论

留言无头像?