有些有助于理解“收益”

在我永恒的追求吮less我试图理解“收益”的声明,但我不断遇到同样的错误。

[someMethod]的主体不能是迭代器块,因为“System.Collections.Generic.List <AClass>”不是迭代器接口types。

这是我卡住的代码:

foreach (XElement header in headersXml.Root.Elements()){ yield return (ParseHeader(header)); } 

我究竟做错了什么? 我不能在迭代器中使用yield吗? 那有什么意义呢? 在这个例子中,它表示List<ProductMixHeader>不是一个迭代器接口types。 ProductMixHeader是一个自定义的类,但我想象List是一个迭代器接口types,不是?

– 编辑 –
感谢所有的快速答案。
我知道这个问题并不是全新的,同样的资源不断涌现。
事实certificate,我想我可以返回List<AClass>作为返回types,但由于List<T>不懒,它不能。 改变我的返回types为IEnumerable<T>解决了这个问题:D

一个有点相关的问题(不值得打开一个新的线程):是否值得给IEnumerable<T>作为返回types,如果我确定99%的情况下我要去.ToList()呢? 性能影响是什么?

使用yield return的方法必须声明为返回以下两个接口之一:

 IEnumerable<SomethingAppropriate> IEnumerator<SomethingApropriate> 

(感谢Jon和Marc指出IEnumerator)

例:

 public IEnumerable<AClass> YourMethod() { foreach (XElement header in headersXml.Root.Elements()) { yield return (ParseHeader(header)); } } 

yield是一个懒惰的数据生成器,只有在第一个被检索后才生成另一个项目,而返回一个列表将一次返回所有的东西。

所以有区别,你需要正确的声明方法。

欲了解更多信息,请阅读乔恩的答案 ,其中包含一些非常有用的链接。

这是一个棘手的话题。 简而言之,这是实现IEnumerable及其朋友的简单方法。 编译器构build一个状态机,将参数和局部variables转换为新类中的实例variables。 复杂的东西。

我有这方面的一些资源:

  • C#深度第六章 (从该页面免费下载)
  • 迭代器,迭代器块和数据pipe道 (文章)
  • 迭代器块的实现细节 (文章)

“yield”创build一个迭代器块 – 一个编译器生成的类,可以实现IEnumerable[<T>]IEnumerator[<T>] 。 Jon Skeet 在“深度C#”的第6章中对这个问题进行了很好的(自由的)讨论。

但基本上 – 要使用“yield”你的方法必须返回一个IEnumerable[<T>]IEnumerator[<T>] 。 在这种情况下:

 public IEnumerable<AClass> SomeMethod() { // ... foreach (XElement header in headersXml.Root.Elements()){ yield return (ParseHeader(header)); } } 

列表实现了Ienumerable。

这里有一个例子可以说明你正在学习的东西。 我写了大约6个月

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace YieldReturnTest { public class PrimeFinder { private Boolean isPrime(int integer) { if (0 == integer) return false; if (3 > integer) return true; for (int i = 2; i < integer; i++) { if (0 == integer % i) return false; } return true; } public IEnumerable<int> FindPrimes() { int i; for (i = 1; i < 2147483647; i++) { if (isPrime(i)) { yield return i; } } } } class Program { static void Main(string[] args) { PrimeFinder primes = new PrimeFinder(); foreach (int i in primes.FindPrimes()) { Console.WriteLine(i); Console.ReadLine(); } Console.ReadLine(); Console.ReadLine(); } } } 

我强烈build议使用Reflector来查看实际yield 。 在使用yield时,您将能够看到编译器为您生成的类的完整代码,并且我发现人们在看到低级结果时能够更快地理解这个概念(嗯,级别我猜)。

为了理解yield ,您需要了解何时使用IEnumeratorIEnumerable (因为您必须使用它们中的任何一个)。 下面的例子可以帮助你理解差异。

首先,看看下面的类,它实现了两个方法 – 一个返回IEnumerator<int> ,一个返回IEnumerable<int> 。 我会告诉你在使用方面有很大的不同,尽pipe2种方法的代码看起来很相似:

 // 2 iterators, one as IEnumerator, one as IEnumerable public class Iterator { public static IEnumerator<int> IterateOne(Func<int, bool> condition) { for(var i=1; condition(i); i++) { yield return i; } } public static IEnumerable<int> IterateAll(Func<int, bool> condition) { for(var i=1; condition(i); i++) { yield return i; } } } 

现在,如果您使用IterateOne您可以执行以下操作:

  // 1. Using IEnumerator allows to get item by item var i=Iterator.IterateOne(x => true); // iterate endless // 1.a) get item by item i.MoveNext(); Console.WriteLine(i.Current); i.MoveNext(); Console.WriteLine(i.Current); // 1.b) loop until 100 int j; while (i.MoveNext() && (j=i.Current)<=100) { Console.WriteLine(j); } 

1.a)打印:

1
2

1.b)打印:

3
4

100

因为在1.a)声明已经执行之后它会继续计数。

您可以看到,您可以使用MoveNext()逐项前进。


相比之下, IterateAll允许您使用foreachLINQ语句来获得更大的舒适度:

  // 2. Using IEnumerable makes looping and LINQ easier var k=Iterator.IterateAll(x => x<100); // limit iterator to 100 // 2.a) Use a foreach loop foreach(var x in k){ Console.WriteLine(x); } // loop // 2.b) LINQ: take 101..200 of endless iteration var lst=Iterator.IterateAll(x=>true).Skip(100).Take(100).ToList(); // LINQ: take items foreach(var x in lst){ Console.WriteLine(x); } // output list 

2.a)打印:

1
2

99

2.b)打印:

101
102

200


注意:由于IEnumerator<T>IEnumerable<T>是generics,因此可以使用任何types。 但是,为了简单起见,我在我的示例中使用了int来表示Ttypes。

这意味着,您可以使用IEnumerator<ProductMixHeader>IEnumerable<ProductMixHeader> (您在问题中提到的自定义类)的返回types之一。

typesList<ProductMixHeader>没有实现任何这些接口,这就是你不能以这种方式使用它的原因。 但例2.b)展示了如何从中创build一个列表。

你使用的方法是什么样的? 我不认为这可以在一个单一的循环中使用。

例如…

 public IEnumerable<string> GetValues() { foreach(string value in someArray) { if (value.StartsWith("A")) { yield return value; } } } 

@I P的答案帮助我理解了产量以及为什么使用它。 yield(yield)的一个(主要)用例是在“in”关键字之后的“foreach”循环中,不返回完整的列表。 不要一次返回完整的列表,在每个“foreach”循环中只返回一个项目(下一个项目)。 所以你会在这种情况下获得收益。 为了更好地理解以下内容,我重写了@I P的代码:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace YieldReturnTest { public class PrimeFinder { private Boolean isPrime(int integer) { if (0 == integer) return false; if (3 > integer) return true; for (int i = 2; i < integer; i++) { if (0 == integer % i) return false; } return true; } public IEnumerable<int> FindPrimesWithYield() { int i; for (i = 1; i < 2147483647; i++) { if (isPrime(i)) { yield return i; } } } public IEnumerable<int> FindPrimesWithoutYield() { var primes = new List<int>(); int i; for (i = 1; i < 2147483647; i++) { if (isPrime(i)) { primes.Add(i); } } return primes; } } class Program { static void Main(string[] args) { PrimeFinder primes = new PrimeFinder(); Console.WriteLine("Finding primes until 7 with yield...very fast..."); foreach (int i in primes.FindPrimesWithYield()) // FindPrimesWithYield DOES NOT iterate over all integers at once, it returns item by item { if (i > 7) { break; } Console.WriteLine(i); //Console.ReadLine(); } Console.WriteLine("Finding primes until 7 without yield...be patient it will take lonkg time..."); foreach (int i in primes.FindPrimesWithoutYield()) // FindPrimesWithoutYield DOES iterate over all integers at once, it returns the complete list of primes at once { if (i > 7) { break; } Console.WriteLine(i); //Console.ReadLine(); } Console.ReadLine(); Console.ReadLine(); } } }