什么是“收益率下降”;用C#做?


493

我已经在MSDN中看到了这种语法:yield break,但我不知道它的作用。有人知道吗?


26
但是,MSDN文档中提到了它,但没有解释。这是取笑知识丰富的开发人员的坏方法。
Phil

3
收益回报消除了对后备列表的需要,也就是说,您不需要像MyList.Add(...)do 一样编写代码yield return ...。如果您需要过早地打破循环并返回虚拟后备列表,请使用yield break;
The Muffin Man

Answers:


529

它指定迭代器已结束。您可以将其yield break视为return不返回值的语句。

例如,如果将函数定义为迭代器,则函数的主体可能如下所示:

for (int i = 0; i < 5; i++)
{
    yield return i;
}

Console.Out.WriteLine("You will see me");

请注意,循环完成所有循环后,将执行最后一行,您将在控制台应用程序中看到该消息。

或者像这样yield break

int i = 0;
while (true)
{
    if (i < 5)
    {
        yield return i;
    }
    else
    {
        // note that i++ will not be executed after this
        yield break;
    }
    i++;
}

Console.Out.WriteLine("Won't see me");

在这种情况下,永远不会执行最后一条语句,因为我们提早离开了函数。


8
可以简单地break代替yield break上面的示例吗?编译器对此没有抱怨。
orad 2014年

85
@orad break在这种情况下,一个简单的操作将停止循环,但不会中止方法的执行,因此将执行最后一行,并且实际上会看到“看不到我的文字”。
DamirZekić2014年

5
@DamirZekić您能否也补充一下答案,为什么您更喜欢收益率突破而不是收益,两者之间有什么区别?
Bruno Costa

11
@DamirZekić返回null和yield break 是不一样的。如果返回null,则可以NullReferenceException获取IEnumerable的枚举数,而使用yield break则不需要(存在没有元素的实例)。
布鲁诺·科斯塔

6
@BrunoCosta尝试在同一方法中使用常规返回会导致编译器错误。使用yield return x警报,编译器希望您将此方法用作创建Enumerator对象的语法糖。此枚举数具有method MoveNext()和property Current。MoveNext()执行该方法直到执行一条yield return语句,然后将该值转换为Current。下次调用MoveNext时,将从此处继续执行。yield break将Current设置为null,表示此枚举器的结束,以便a foreach (var x in myEnum())将结束。
Wolfzoon

57

结束迭代器块(例如,说IEnumerable中没有更多元素)。


2
[+1]-尽管从理论上讲,.NET中没有迭代器。仅枚举数(一个方向,向前。)
肖恩·威尔逊

@Shaun Wilson,但是使用yield关键字您可以双向迭代集合,此外,您可以连续使用集合的每个下一个元素
monstr

@monstr您可以以任何方式迭代集合,而无需yield这样做。我并不是说你错了,而是我明白你的建议。但是,从理论上讲,.NET中没有迭代器,只有枚举器(一个方向,向前)-与stdc ++不同,CTS / CLR中没有定义“通用迭代器框架”。LINQ通过利用扩展方法yield return 以及回调方法来帮助缩小差距,但是它们是一流的扩展方法,而不是一流的迭代器。结果IEnumerable本身除了向调用方转发以外,本身无法在任何方向上进行迭代。
肖恩·威尔逊

29

告诉迭代器已到达终点。

举个例子:

public interface INode
{
    IEnumerable<Node> GetChildren();
}

public class NodeWithTenChildren : INode
{
    private Node[] m_children = new Node[10];

    public IEnumerable<Node> GetChildren()
    {
        for( int n = 0; n < 10; ++n )
        {
            yield return m_children[ n ];
        }
    }
}

public class NodeWithNoChildren : INode
{
    public IEnumerable<Node> GetChildren()
    {
        yield break;
    }
}

21

yield基本上使IEnumerable<T>方法的行为类似于协作式(而不是抢先式)调度线程。

yield return就像调用“计划”或“睡眠”功能的线程放弃了对CPU的控制。就像线程一样,该IEnumerable<T>方法在紧随其后的位置重新获得控制,所有局部变量的值与放弃控制之前的值相同。

yield break 就像一个线程到达其功能的结尾并终止。

人们谈论“状态机”,但是状态机实际上就是“线程”。线程具有某种状态(即局部变量的值),并且每次对其进行调度时,它都会采取一些操作以达到新的状态。关键yield在于,与我们习惯的操作系统线程不同,使用该代码的代码会及时冻结,直到手动推进或终止迭代为止。



9

yield break语句导致枚举停止。实际上,yield break完成了枚举而不返回任何其他项目。

考虑一下,迭代器方法实际上可以通过两种方式停止迭代。在一种情况下,方法的逻辑可以在返回所有项目后自然退出方法。这是一个例子:

IEnumerable<uint> FindPrimes(uint startAt, uint maxCount)
{
    for (var i = 0UL; i < maxCount; i++)
    {
        startAt = NextPrime(startAt);
        yield return startAt;
    }

    Debug.WriteLine("All the primes were found.");
}

在上面的示例中,一旦maxCount找到素数,迭代器方法自然会停止执行。

yield break语句是迭代器停止枚举的另一种方式。这是尽早突破枚举的一种方法。这是与上述相同的方法。这次,该方法对方法可以执行的时间有限制。

IEnumerable<uint> FindPrimes(uint startAt, uint maxCount, int maxMinutes)
{
    var sw = System.Diagnostics.Stopwatch.StartNew();
    for (var i = 0UL; i < maxCount; i++)
    {
        startAt = NextPrime(startAt);
        yield return startAt;

        if (sw.Elapsed.TotalMinutes > maxMinutes)
            yield break;
    }

    Debug.WriteLine("All the primes were found.");
}

请注意对的调用yield break。实际上,它正在尽早退出枚举。

yield break还要注意,作品的作用与普通作品不同break。在上面的示例中,yield break退出方法而不调用Debug.WriteLine(..)


7

这里http://www.alteridem.net/2007/08/22/the-yield-statement-in-c/是一个很好的例子:

公共静态IEnumerable <int> Range(int min,int max)
{
   而(true)
   {
      如果(min> = max)
      {
         产量中断;
      }
      收益回报率最低++;
   }
}

和解释,如果yield break在方法中命中了一条语句,则该方法的执行将停止且不会返回。在某些情况下,当您不想给出任何结果时,可以使用yield break。


5

yield yield只是最后一次回报的一种方式,不返回任何值

例如

// returns 1,2,3,4,5
IEnumerable<int> CountToFive()
{
    yield return 1;
    yield return 2;
    yield return 3;
    yield return 4;
    yield return 5;
    yield break;
    yield return 6;
    yield return 7;
    yield return 8;
    yield return 9;
 }

4

如果您所说的“真正的收益突破是什么”,则是“如何运作”-有关详细信息,请参见Raymond Chen的博客。 https://devblogs.microsoft.com/oldnewthing/20080812-00/?p=21273

C#迭代器生成一些非常复杂的代码。


12
好吧,只有当您关心它们所编译的代码时,它们才会变得复杂。使用它们的代码非常简单。
罗伯特·罗斯尼

@Robert,这就是我的意思,所以我更新了答案以包含您的评论
Tony Lee,

-2

yield关键字与return关键字一起使用,可为枚举数对象提供一个值。yield return指定返回的一个或多个值。到达yield return语句时,将存储当前位置。下次调用迭代器时,将从该位置重新开始执行。

用一个例子来解释其含义:

    public IEnumerable<int> SampleNumbers()
    {
        int counter = 0;
        yield return counter;

        counter = counter + 2;

        yield return counter;

        counter = counter + 3;

        yield return counter ;
    }

迭代时返回的值为:0、2、5。

重要的是要注意,此示例中的计数器变量是局部变量。在第二次迭代返回值2之后,第三次迭代从它之前离开的位置开始,同时保留名为counter的局部变量的先前值2。


11
你没有解释什么yield break
杰伊沙利文

我认为yield return实际上不支持返回多个值。也许那不是你真正的意思,但这就是我的阅读方式。
山姆

Sam-具有多个yield return语句的SampleNumbers方法实际上确实有效,迭代器的值立即返回,并在请求下一个值时恢复执行。我见过人们以“屈服中断”结束这种方法,但这是不必要的。敲定方法的结尾也将结束迭代器
Loren Paulsen

这是一个较差的示例的原因是yield break它不包含语言级别的枚举器,例如foreach-使用枚举器时yield break提供了真正的价值。这个例子看起来像是一个展开的循环。您几乎在现实世界中几乎看不到此代码(当然,我们都可以想到一些边缘情况),这里没有“迭代器”。根据语言规范,“迭代器块”不能扩展到该方法之外。什么是真正被返回是一个“枚举”,也见:stackoverflow.com/questions/742497/...
肖恩·威尔逊
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.