35

在我不断追求减少吸吮的过程中,我试图理解“屈服”声明,但我一直遇到同样的错误。

[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

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

4

8 回答 8

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 的回答,其中包含一些非常有用的链接。

于 2008-11-25T14:16:41.570 回答
15

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

我有这方面的一些资源:

于 2008-11-25T14:23:00.733 回答
8

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

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

public IEnumerable<AClass> SomeMethod() {
    // ...
    foreach (XElement header in headersXml.Root.Elements()){
        yield return (ParseHeader(header));                
    }
}
于 2008-11-25T14:21:10.597 回答
3

List 实现了 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();
        }
    }
}
于 2008-11-25T14:21:03.857 回答
3

我强烈建议使用Reflector来看看yield实际对你有什么作用。使用 yield 时,您将能够看到编译器为您生成的类的完整代码,而且我发现人们在看到低级结果时会更快地理解这个概念(嗯,中 -我猜的水平)。

于 2008-11-25T14:27:29.770 回答
2

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

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

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

    // 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在示例中使用了 type T

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

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

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

所有示例的 DotNetFiddle

于 2015-06-17T09:13:20.297 回答
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();
        }
    }
}
于 2014-07-11T07:42:05.147 回答
0

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

例如...

public IEnumerable<string> GetValues() {
    foreach(string value in someArray) {
        if (value.StartsWith("A")) { yield return value; }
    }
}
于 2008-11-25T14:16:48.420 回答