【发布时间】:2010-10-19 01:19:03
【问题描述】:
我想以易于理解的形式了解yield 语句的所有内容。
我已经阅读了yield 语句及其在实现迭代器模式时的易用性。但是,大部分都非常干燥。我想深入了解一下微软如何处理收益率。
另外,你什么时候使用yield break?
【问题讨论】:
我想以易于理解的形式了解yield 语句的所有内容。
我已经阅读了yield 语句及其在实现迭代器模式时的易用性。但是,大部分都非常干燥。我想深入了解一下微软如何处理收益率。
另外,你什么时候使用yield break?
【问题讨论】:
yield 通过在内部构建状态机来工作。它会在例程退出时存储该例程的当前状态,并在下次从该状态恢复时存储。
您可以使用 Reflector 查看编译器是如何实现的。
yield break 在您想停止返回结果时使用。如果您没有yield break,编译器会在函数末尾假定一个(就像普通函数中的return; 语句)
【讨论】:
正如 Mehrdad 所说,它构建了一个状态机。
除了使用 Reflector(另一个很好的建议)之外,您可能会发现 my article on iterator block implementation 很有用。如果不是 finally 块,它会相对简单 - 但它们引入了一个完整的额外维度!
【讨论】:
让我们稍微回顾一下:yield 关键字被翻译成许多其他人对状态机所说的那样。
实际上,这并不完全像使用将在幕后使用的内置实现,而是编译器通过实现一个相关接口(返回类型包含yield 关键字的方法)。
A(有限)state machine 只是一段代码,取决于您在代码中的位置(取决于先前的状态,输入)转到另一个状态操作,这几乎就是您在正在使用方法返回类型为 IEnumerator<T> / IEnumerator 的 yield。 yield 关键字将创建另一个动作以从前一个状态移动到下一个状态,因此状态管理是在 MoveNext() 实现中创建的。
这正是 C# 编译器 / Roslyn 要做的事情:检查是否存在 yield 关键字以及包含方法的返回类型,是否是 IEnumerator<T>、IEnumerable<T>、@987654338 @ 或 IEnumerable 然后创建一个反映该方法的私有类,集成必要的变量和状态。
如果您对状态机的详细信息以及编译器如何重写迭代感兴趣,可以在 Github 上查看这些链接:
琐事 1:AsyncRewriter(在编写 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中翻译为:
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;
}
正如您所见,这个实现远非简单,但它确实可以完成工作!
琐事 2: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
它指定一个迭代器已经结束。你可以想到 yield break 作为一个不返回值的 return 语句。
【讨论】: