【问题标题】:What does "yield break;" do in C#?"yield break" 是什么意思?在 C# 中做?
【发布时间】:2020-12-19 08:44:30
【问题描述】:

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

【问题讨论】:

  • 收益回报消除了对支持列表的需要,即您不需要编写类似MyList.Add(...) 的代码,只需执行yield return ...。如果您需要提前跳出循环并返回您使用的虚拟后备列表yield break;

标签: c# .net yield


【解决方案1】:

它指定一个迭代器已经结束。您可以将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");

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

【讨论】:

  • 在上面的示例中可以简单地使用break 而不是yield break 吗?编译器不会对此抱怨。
  • @orad 一个简单的break 在这种情况下会停止循环,但它不会中止方法执行,因此最后一行将被执行并且“不会看到我的文本”实际上会被看到。
  • @Damir Zekić 你能否在你的答案中补充一下为什么你应该更喜欢收益突破而不是回报,两者有什么区别?
  • @DamirZekić 返回 null 和 yield break 相同。如果您返回 null,那么您可以让 NullReferenceException 获取 IEnumerable 的枚举器,而使用 yield break 时您不会(有一个没有元素的实例)。
  • @BrunoCosta 尝试在同一方法中定期返回会导致编译器错误。使用yield return x 会提醒编译器您希望此方法成为创建Enumerator 对象的语法糖。这个枚举器有方法MoveNext() 和属性Current。 MoveNext() 执行该方法直到出现yield return 语句,并将该值转换为Current。下次调用 MoveNext 时,从那里继续执行。 yield break 将 Current 设置为 null,表示此枚举器的结束,因此 foreach (var x in myEnum()) 将结束。
【解决方案2】:

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

【讨论】:

  • with [+1] -- 尽管在学术上 .NET 中没有迭代器。只有枚举器(一个方向,向前)。
  • @Shaun Wilson,但使用 yield 关键字,您可以双向迭代集合,而且您可以不连续获取集合的每个下一个元素
  • @monstr 你可以以任何方式迭代集合,你不需要yield 来做。我不是说你错了,我明白你的建议;但是,从学术上讲,.NET 中没有迭代器,只有枚举器(单向,向前)——与 stdc++ 不同,CTS/CLR 中没有定义“通用迭代器框架”。 LINQ 有助于缩小利用yield return 以及回调方法的扩展方法的差距,但它们是一流的扩展方法,而不是一流的迭代器。生成的 IEnumerable 本身不能在任何方向上迭代,除了向前调用者。
【解决方案3】:

告诉迭代器它已经到了结尾。

举个例子:

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;
    }
}

【讨论】:

    【解决方案4】:

    yield 基本上使IEnumerable&lt;T&gt; 方法的行为类似于协作(而不是抢占式)调度线程。

    yield return 就像一个线程调用“调度”或“睡眠”函数来放弃对 CPU 的控制。就像线程一样,IEnumerable&lt;T&gt; 方法在之后立即重新获得控制权,所有局部变量的值与放弃控制权之前的值相同。

    yield break 就像一个线程到达其函数的末尾并终止。

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

    【讨论】:

      【解决方案5】:

      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(..)

      【讨论】:

        【解决方案6】:

        yield break 只是表示最后一次返回并且不返回任何值的一种方式

        例如

        // 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;
         }
        

        【讨论】:

          【解决方案7】:

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

          公共静态 IEnumerable Range(int min, int max) { 而(真) { 如果 ( 最小值 >= 最大值 ) { 产量中断; } 收益返回 min++; } }

          并解释说,如果在方法中遇到yield break 语句,则该方法的执行将停止且不返回。有一些时间情况,当你不想给出任何结果时,你可以使用yield break。

          【讨论】:

            【解决方案8】:

            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。

            需要注意的是,这个例子中的counter变量是一个局部变量。在第二次迭代返回值2之后,第三次迭代从它离开的地方开始之前,同时保留名为 counter 的局部变量的先前值为 2。

            【讨论】:

            • 你没有解释yield break做了什么
            • 我不认为yield return 实际上支持返回多个值。也许这不是你真正的意思,但我就是这么读的。
            • Sam -- 具有多个 yield return 语句的 SampleNumbers 方法实际上确实有效,迭代器的值立即返回,并在请求下一个值时恢复执行。我见过人们用“yield break”来结束这样的方法,但这是不必要的。击中方法的结尾也结束了迭代器
            • 这是yield break 的一个糟糕示例的原因是它不包含诸如foreach 之类的语言级枚举器——当使用枚举器时yield break 提供了真正的价值。这个例子看起来像一个展开的循环。你几乎永远不会在现实世界中看到这段代码(当然,我们都可以想到一些边缘情况)而且,这里没有“迭代器”。作为语言规范的问题,“迭代器块”不能超出方法。实际返回的是“可枚举”,另请参见:stackoverflow.com/questions/742497/…
            猜你喜欢
            • 2017-09-12
            • 2020-06-28
            • 2011-12-11
            • 2019-03-25
            • 1970-01-01
            • 2011-12-27
            • 2013-10-31
            相关资源
            最近更新 更多