我已经看到在Stack Overflow和博客上使用了yield关键字.我不使用LINQ.有人可以解释yield关键字吗?
我知道存在类似的问题.但是没有一个能用简单的语言解释它的用途.
到目前为止,对此(我见过)的最佳解释是Jon Skeet的书 - 那一章是免费的!第6章,C#深入.我无法在这里添加任何未涵盖的内容.
然后买书; 你会成为一个更好的C#程序员.
问:为什么我不在这里写一个更长的答案(从评论中复述); 简单.正如Eric Lippert观察到的那样(这里),yield
构造(以及它背后的魔力)是C#编译器中最复杂的代码,并且在一个简短的回复中尝试和描述它至多是天真的.yield
IMO 有很多细微差别,最好引用预先存在的(并且完全合格的)资源.
Eric的博客现在有7个条目(这只是最近的条目)讨论yield
.我有一个广阔的埃里克尊重的金额,但他的博客可能更适合作为"更多信息"的人谁是舒适的主题(yield
在这种情况下),因为它通常描述了很多背景的设计考虑.最好在合理的基础上完成.
(是的,第6章确实下载了;我验证了......)
所述yield
关键字被用于与返回的方法IEnumerable
或IEnumerator
它使编译器生成实现必需的管道使用迭代器的类.例如
public IEnumeratorSequenceOfOneToThree() { yield return 1; yield return 2; yield return 3; }
由于编译器将生成一个实现类以上IEnumerator
,IEnumerable
并且IDisposable
(实际上它也将实施的非通用版本IEnumerable
和IEnumerator
).
这使您可以调用该方法SequenceOfOneToThree
在foreach
这样的循环
foreach(var number in SequenceOfOneToThree) { Console.WriteLine(number); }
迭代器是状态机,因此每次yield
调用方法中的位置都会被记录.如果迭代器移动到下一个元素,则该方法在此位置后立即恢复.所以第一次迭代返回1并标记该位置.下一个迭代器在一个之后重新开始,因此返回2,依此类推.
不用说,您可以以任何您喜欢的方式生成序列,因此您不必像我那样对数字进行硬编码.此外,如果你想打破循环,你可以使用yield break
.
为了揭开神秘面孔,我将避免谈论迭代器,因为它们本身可能是神秘的一部分.
收益率收益和收益率中断语句通常用于提供收集的"延期评估".
这意味着当你得到一个使用yield return的方法的值时,你想要获得的东西的集合不会一起存在(它本质上是空的).当你遍历它们(使用foreach)时,它将在那时执行方法并获取枚举中的下一个元素.
某些属性和方法将导致立即计算整个枚举(例如"计数").
以下是返回集合和返回yield之间差异的快速示例:
string[] names = { "Joe", "Jim", "Sam", "Ed", "Sally" }; public IEnumerableGetYieldEnumerable() { foreach (var name in names) yield return name; } public IEnumerable GetList() { var list = new List (); foreach (var name in names) list.Add(name); return list; } // we're going to execute the GetYieldEnumerable() method // but the foreach statement inside it isn't going to execute var yieldNames = GetNamesEnumerable(); // now we're going to execute the GetList() method and // the foreach method will execute var listNames = GetList(); // now we want to look for a specific name in yieldNames. // only the first two iterations of the foreach loop in the // GetYieldEnumeration() method will need to be called to find it. if (yieldNames.Contains("Jim") Console.WriteLine("Found Jim and only had to loop twice!"); // now we'll look for a specific name in listNames. // the entire names collection was already iterated over // so we've already paid the initial cost of looping through that collection. // now we're going to have to add two more loops to find it in the listNames // collection. if (listNames.Contains("Jim")) Console.WriteLine("Found Jim and had to loop 7 times! (5 for names and 2 for listNames)");
如果您需要在源数据具有值之前获取对枚举的引用,也可以使用此方法.例如,如果名称集合未完成,则开头:
string[] names = { "Joe", "Jim", "Sam", "Ed", "Sally" }; public IEnumerableGetYieldEnumerable() { foreach (var name in names) yield return name; } public IEnumerable GetList() { var list = new List (); foreach (var name in names) list.Add(name); return list; } var yieldNames = GetNamesEnumerable(); var listNames = GetList(); // now we'll change the source data by renaming "Jim" to "Jimbo" names[1] = "Jimbo"; if (yieldNames.Contains("Jimbo") Console.WriteLine("Found Jimbo!"); // Because this enumeration was evaluated completely before we changed "Jim" // to "Jimbo" it isn't going to be found if (listNames.Contains("Jimbo")) // this can't be true else Console.WriteLine("Couldn't find Jimbo, because he wasn't there when I was evaluated.");
该yield
关键字是编写一个方便的途径IEnumerator
.例如:
public static IEnumeratorRange(int from, int to) { for (int i = from; i < to; i++) { yield return i; } }
由C#编译器转换为类似于:
public static IEnumeratorRange(int from, int to) { return new RangeEnumerator(from, to); } class RangeEnumerator : IEnumerator { private int from, to, current; public RangeEnumerator(int from, int to) { this.from = from; this.to = to; this.current = from; } public bool MoveNext() { this.current++; return this.current < this.to; } public int Current { get { return this.current; } } }
请查看MSDN文档和示例.它本质上是一种在C#中创建迭代器的简单方法.
public class List { //using System.Collections; public static IEnumerable Power(int number, int exponent) { int counter = 0; int result = 1; while (counter++ < exponent) { result = result * number; yield return result; } } static void Main() { // Display powers of 2 up to the exponent 8: foreach (int i in Power(2, 8)) { Console.Write("{0} ", i); } } }