C#中的"yield break;"是什么意思?

591

我在 MSDN 上看到了这个语法:yield break,但我不知道它是什么意思。有人知道吗?


4
yield return 可以消除需要使用后台列表的需求,也就是说你不需要编写类似于 MyList.Add(...) 的代码,只需使用 yield return ...。如果你需要提前退出循环并返回虚拟后台列表,则可以使用 yield break; - The Muffin Man
9个回答

628

这表示迭代器已经结束。你可以把 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");

在这种情况下,由于我们提前离开了该函数,因此最后一条语句不会被执行。

13
在您上面的示例中,使用break而不是yield break是否可以呢?编译器对此没有投诉。 - orad
100
在这种情况下,简单的break语句会停止循环,但不会中止方法的执行,因此最后一行代码将被执行,"Won't see me text"实际上会被显示出来。 - Damir Zekić
5
你好,以下是你需要翻译的内容:@Damir Zekić,您能否在回答中解释一下为什么应该优先选择yield break而不是return,并且两者之间的区别是什么? - Bruno Costa
11
@DamirZekić,返回null和使用yield break并不相同。如果返回null,则在获取IEnumerable的枚举器时可能会出现"NullReferenceException"异常,而使用yield break则不会出现该异常(因为存在一个没有元素的实例)。 - Bruno Costa
6
在同一方法中尝试实现定期返回会导致编译错误。使用yield return x会提示编译器,您希望此方法成为创建Enumerator对象的语法糖。此枚举器具有MoveNext() 方法和 Current 属性。MoveNext() 执行该方法直到出现 yield return 语句,并将该值转换为 Current。下一次调用 MoveNext() 时,执行将从那里继续。yield break 将 Current 设置为 null,表示此枚举器的结束,这样 foreach (var x in myEnum()) 将会结束。 - Wolfzoon
显示剩余6条评论

70

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


3
虽然在.NET中没有迭代器,只有枚举器(单向,前向),但使用“+1”可以实现类似于迭代器的功能。 - Shaun Wilson
@Shaun Wilson,但是使用yield关键字,您可以在两个方向上迭代集合,而且您可以不按顺序获取集合的每个下一个元素。 - monstr
@monstr,您可以以任何方式迭代集合,并且不需要使用yield。我不是说您错了,我知道您的建议; 但是,从学术上讲,在.NET中没有迭代器,只有枚举器(单向,向前)-与stdc ++ 不同,在CTS / CLR中没有定义“通用迭代器框架”。LINQ通过利用yield return 和回调方法的扩展方法来缩小差距,但它们是一流的扩展方法,而不是一流的迭代器。生成的IEnumerable本身不能在调用者方面以任何方向进行迭代。 - Shaun Wilson

33

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

例如:

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

32

yield 基本上使得 IEnumerable<T> 方法表现得像一个协作(而非抢占式)调度线程。

yield return 就像线程调用 “schedule” 或 “sleep” 函数以放弃对 CPU 的控制。就像线程一样,IEnumerable<T> 方法在此之后立即重新获得控制,并且所有局部变量的值与放弃控制前相同。

yield break 就像线程到达函数结尾并终止。

人们谈论“状态机”,但实际上一个“线程”就是一个状态机。线程有一些状态(即局部变量的值),每次被调度时它会执行一些操作以达到新状态。关于 yield 的关键点在于,不同于我们所熟悉的操作系统线程,使用它的代码在迭代被手动推进或终止之前都处于冻结状态。


17

yield break只是一种方式,表示最后一次执行return,并且不返回任何值。

例如:

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

16

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


8
这里有一个非常好的例子:http://www.alteridem.net/2007/08/22/the-yield-statement-in-c/
public static IEnumerable<int> Range( int min, int max )
{
   while ( true )
   {
      if ( min >= max )
      {
         yield break;
      }
      yield return min++;
   }
}
如果在方法中遇到yield break语句,则该方法的执行将停止,不会返回任何结果。有时候你不想返回任何结果,那么就可以使用yield break。

0
除了其他好的答案之外,请注意yield break在嵌套循环中的工作方式与普通的break不同。当break只会停止当前循环而不会停止任何外部循环时,yield break将停止整个枚举:
IEnumerable<int> Iterate() {
  for(int i=0; i<5; i++) {
    yield return i;

    for(int j=0; j<5; j++) {
      if ((i*10 + j) > 30)
        // This will stop the whole enumeration, even if there's
        // an outer "for" loop
        yield break;

      yield return (i*10 + j);
    }
  }
}

Console.WriteLine(string.Join(", ", Iterate().Select(i => i.ToString())));
// 0, 0, 1, 2, 3, 4, 1, 10, 11, 12, 13, 14, 2, 20, 21, 22, 23, 24, 3, 30

-4

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)。


11
您没有解释 yield break 的作用。 - Jay Sullivan
我认为 yield return 实际上不支持返回多个值。也许这不是你实际想表达的,但这是我理解的方式。 - Sam
Sam--拥有多个yield return语句的SampleNumbers方法确实可以工作,迭代器的值会立即返回,并在请求下一个值时恢复执行。我看过有人以“yield break”来结束这样的方法,但这是不必要的。到达方法末尾也将结束迭代器。 - Loren Paulsen
这个例子不适用于 yield break 的原因是它不包含语言级别的枚举器,例如 foreach -- 使用枚举器时,yield break 提供了真正的价值。这个例子看起来像是一个未展开的循环。你几乎永远不会在现实世界中看到这段代码(当然我们都可以想到一些边缘情况)。此外,这里没有“迭代器”,“迭代器块”不能超出方法作为语言规定的事情。实际返回的是一个“可枚举对象”,请参阅:https://dev59.com/eXRB5IYBdhLWcg3wCjcV - Shaun Wilson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接