一些有助于理解“产量”

时间:2008-11-25 14:12:53

标签: c# iterator yield

在我不断追求吸吮的过程中,我试图理解“收益”陈述,但我一直遇到同样的错误。

  

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

这是我遇到的代码:

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

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

- 编辑 -
感谢所有快速解答。
我知道这个问题不是那么新,而且同样的资源不断涌现 结果我认为我可以返回List<AClass>作为返回类型,但由于List<T>不是懒惰,它不能。将我的返回类型更改为IEnumerable<T>解决了问题:D

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

8 个答案:

答案 0 :(得分:32)

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

IEnumerable<SomethingAppropriate>
IEnumerator<SomethingApropriate>

(感谢JonMarc指出IEnumerator)

示例:

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

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

因此存在差异,您需要正确声明方法。

有关详细信息,请阅读Jon's answer here,其中包含一些非常有用的链接。

答案 1 :(得分:15)

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

我有一些资源:

答案 2 :(得分:8)

“yield”创建一个迭代器块 - 一个编译器生成的类,可以实现IEnumerable[<T>]IEnumerator[<T>]。 Jon Skeet在C# in Depth的第6章中对此进行了非常好的(并且是免费的)讨论。

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

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

答案 3 :(得分:3)

列表实现了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();
        }
    }
}

答案 4 :(得分:3)

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

答案 5 :(得分:2)

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

首先,看看下面的类,它实现了两个方法 - 一个返回IEnumerator<int>,一个返回IEnumerable<int>。我会告诉你,虽然2种方法的代码看起来很相似但使用方法有很大差异:

// 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允许您使用foreach以及 LINQ 语句以获得更大的舒适度:

    // 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>是泛型,因此可以使用任何类型。但是,为简单起见,我在示例中使用了int类型T

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

类型List<ProductMixHeader>未实现任何这些接口,这就是您不能以这种方式使用它的原因。但示例2.b)显示了如何从中创建列表。

如果您通过附加.ToList()来创建列表,那么暗示是,它将创建内存中所有元素的列表,而IEnumerable允许延迟创建其元素 - 就性能而言,这意味着元素及时枚举 - 尽可能晚,但只要您使用.ToList(),就会在内存中创建所有元素。 LINQ尝试在幕后以这种方式优化性能。

<强> DotNetFiddle of all examples

答案 6 :(得分:1)

@Ian P的回答帮助我理解了产量及其使用原因。 yield的一个(主要)用例是在“in”关键字之后的“foreach”循环中,不返回完整列表。而不是一次返回完整列表,在每个“foreach”循环中只返回一个项目(下一个项目)。因此,在这种情况下,您将获得收益率的表现。 我已经重写了@Ian 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();
        }
    }
}

答案 7 :(得分:0)

你使用它的方法是什么样的?我不认为这可以单独用于循环。

例如......

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