我希望以易于理解的形式了解有关yield
语句的所有内容。
我已经阅读了yield
语句及其在实现迭代器模式时的易用性。但是,大部分都非常干燥。我想深入了解微软如何处理收益率。
另外,你什么时候使用yield break?
答案 0 :(得分:11)
yield
通过在内部构建状态机来工作。它会在退出时存储例程的当前状态,并在下次从该状态恢复。
您可以使用Reflector查看编译器如何实现它。
当您想要停止返回结果时,使用 yield break
。如果你没有yield break
,编译器会在函数末尾假设一个(就像正常函数中的return;
语句一样)
答案 1 :(得分:11)
这是一个开始,来自Raymond Chen的博客:
答案 2 :(得分:8)
正如Mehrdad所说,它构建了一个状态机。
除了使用Reflector(另一个很好的建议),你可能会发现my article on iterator block implementation很有用。如果它不是finally
块,它将是相对简单 - 但它们引入了一个额外的复杂维度!
答案 3 :(得分:3)
让我们回顾一下:yield
关键字的翻译与对状态机所说的其他翻译一样多。
实际上,这并不完全像在幕后使用内置的实现,而是编译器通过实现一个相关接口(返回类型)将yield
相关代码重写为状态机。包含yield
关键字的方法)。
(有限的)state machine只是一段代码,它取决于您在代码中的位置(取决于先前的状态,输入)转到另一个状态操作,这几乎是正在发生的事情当您使用并让方法返回类型为IEnumerator<T>
/ IEnumerator
时。 yield
关键字是用来创建另一个动作以从上一个状态移至下一个状态的关键字,因此状态管理是在MoveNext()
实现中创建的。
这正是C#编译器/ Roslyn所要做的:检查yield
关键字的存在以及包含方法的返回类型,是否为IEnumerator<T>
,{{ 1}},IEnumerable<T>
或IEnumerator
,然后创建一个反映该方法的私有类,并集成必要的变量和状态。
如果您对状态机以及编译器如何重写迭代的细节感兴趣,可以在Github上查看这些链接:
琐事1 :AsyncRewriter
(在您编写IEnumerable
/ async
代码时使用,它也继承自await
,因为它还利用了状态机器落后。
如前所述,状态机在StateMachineRewriter
生成的实现中得到了很大的体现,其中有一个bool MoveNext()
+有时基于代表不同状态的状态字段,有些老式的switch
在您的方法中执行到不同状态的路径。
由编译器从用户代码生成的代码看起来并不“好”,主要是因为编译器到处都添加了一些奇怪的前缀和后缀
例如,代码:
goto
与上面那段代码相关的变量和类型在编译后将如下所示:
public class TestClass
{
private int _iAmAHere = 0;
public IEnumerator<int> DoSomething()
{
var start = 1;
var stop = 42;
var breakCondition = 34;
var exceptionCondition = 41;
var multiplier = 2;
// Rest of the code... with some yield keywords somewhere below...
关于状态机本身,让我们看一个非常简单的示例,其中带有一个伪分支,用于产生一些偶数/奇数的东西。
public class TestClass
{
[CompilerGenerated]
private sealed class <DoSomething>d__1 : IEnumerator<int>, IDisposable, IEnumerator
{
// Always present
private int <>1__state;
private int <>2__current;
// Containing class
public TestClass <>4__this;
private int <start>5__1;
private int <stop>5__2;
private int <breakCondition>5__3;
private int <exceptionCondition>5__4;
private int <multiplier>5__5;
将在public class Example
{
public IEnumerator<string> DoSomething()
{
const int start = 1;
const int stop = 42;
for (var index = start; index < stop; index++)
{
yield return index % 2 == 0 ? "even" : "odd";
}
}
}
中翻译为:
MoveNext
如您所见,此实现远非简单易行,但确实可以完成!
琐事2 :private bool MoveNext()
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<start>5__1 = 1;
<stop>5__2 = 42;
<index>5__3 = <start>5__1;
break;
case 1:
<>1__state = -1;
goto IL_0094;
case 2:
{
<>1__state = -1;
goto IL_0094;
}
IL_0094:
<index>5__3++;
break;
}
if (<index>5__3 < <stop>5__2)
{
if (<index>5__3 % 2 == 0)
{
<>2__current = "even";
<>1__state = 1;
return true;
}
<>2__current = "odd";
<>1__state = 2;
return true;
}
return false;
}
/ IEnumerable
方法的返回类型会发生什么?
好吧,它不仅会生成同时实现IEnumerable<T>
和IEnumerator<T>
的类,而且会生成一个同时实现IEnumerable<T>
和IEnumerator<T>
的类,以便实现IEnumerator<T> GetEnumerator()
利用相同的生成类。
关于使用yield
关键字时自动实现的几个接口的温馨提醒:
public interface IEnumerable<out T> : IEnumerable
{
new IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> : IDisposable, IEnumerator
{
T Current { get; }
}
public interface IEnumerator
{
bool MoveNext();
object Current { get; }
void Reset();
}
您还可以检出this example,具有不同的路径/分支和编译器重写的完整实现。
这是用SharpLab创建的,您可以使用该工具尝试不同的yield
相关的执行路径,并查看编译器如何在MoveNext
中将它们重写为状态机。实施。
关于问题的第二部分yield break
,已经回答了here
它指定迭代器已结束。你可以想到 产生中断作为不返回值的返回语句。