【发布时间】:2020-12-19 08:44:30
【问题描述】:
我在 MSDN 中看到过这种语法:yield break,但我不知道它的作用。有人知道吗?
【问题讨论】:
-
收益回报消除了对支持列表的需要,即您不需要编写类似
MyList.Add(...)的代码,只需执行yield return ...。如果您需要提前跳出循环并返回您使用的虚拟后备列表yield break;
我在 MSDN 中看到过这种语法:yield break,但我不知道它的作用。有人知道吗?
【问题讨论】:
MyList.Add(...) 的代码,只需执行yield return ...。如果您需要提前跳出循环并返回您使用的虚拟后备列表yield break;
它指定一个迭代器已经结束。您可以将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 吗?编译器不会对此抱怨。
break 在这种情况下会停止循环,但它不会中止方法执行,因此最后一行将被执行并且“不会看到我的文本”实际上会被看到。
NullReferenceException 获取 IEnumerable 的枚举器,而使用 yield break 时您不会(有一个没有元素的实例)。
yield return x 会提醒编译器您希望此方法成为创建Enumerator 对象的语法糖。这个枚举器有方法MoveNext() 和属性Current。 MoveNext() 执行该方法直到出现yield return 语句,并将该值转换为Current。下次调用 MoveNext 时,从那里继续执行。 yield break 将 Current 设置为 null,表示此枚举器的结束,因此 foreach (var x in myEnum()) 将结束。
结束一个迭代器块(例如,表示 IEnumerable 中没有更多元素)。
【讨论】:
yield 关键字,您可以双向迭代集合,而且您可以不连续获取集合的每个下一个元素
yield 来做。我不是说你错了,我明白你的建议;但是,从学术上讲,.NET 中没有迭代器,只有枚举器(单向,向前)——与 stdc++ 不同,CTS/CLR 中没有定义“通用迭代器框架”。 LINQ 有助于缩小利用yield return 以及回调方法的扩展方法的差距,但它们是一流的扩展方法,而不是一流的迭代器。生成的 IEnumerable 本身不能在任何方向上迭代,除了向前调用者。
告诉迭代器它已经到了结尾。
举个例子:
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;
}
}
【讨论】:
yield 基本上使IEnumerable<T> 方法的行为类似于协作(而不是抢占式)调度线程。
yield return 就像一个线程调用“调度”或“睡眠”函数来放弃对 CPU 的控制。就像线程一样,IEnumerable<T> 方法在之后立即重新获得控制权,所有局部变量的值与放弃控制权之前的值相同。
yield break 就像一个线程到达其函数的末尾并终止。
人们谈论“状态机”,但状态机实际上就是“线程”。一个线程有一些状态(即局部变量的值),每次调度它都会采取一些动作来达到一个新的状态。 yield 的关键在于,与我们习惯的操作系统线程不同,使用它的代码会及时冻结,直到手动推进或终止迭代。
【讨论】:
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(..)。
【讨论】:
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;
}
【讨论】:
这里http://www.alteridem.net/2007/08/22/the-yield-statement-in-c/是一个很好的例子:
公共静态 IEnumerable并解释说,如果在方法中遇到yield break 语句,则该方法的执行将停止且不返回。有一些时间情况,当你不想给出任何结果时,你可以使用yield break。
【讨论】:
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 实际上支持返回多个值。也许这不是你真正的意思,但我就是这么读的。
yield break 的一个糟糕示例的原因是它不包含诸如foreach 之类的语言级枚举器——当使用枚举器时yield break 提供了真正的价值。这个例子看起来像一个展开的循环。你几乎永远不会在现实世界中看到这段代码(当然,我们都可以想到一些边缘情况)而且,这里没有“迭代器”。作为语言规范的问题,“迭代器块”不能超出方法。实际返回的是“可枚举”,另请参见:stackoverflow.com/questions/742497/…