Meh*_*ari 11
yield通过在内部构建状态机来工作.它存储例程退出时的当前状态,并在下次从该状态恢复.
您可以使用Reflector查看编译器如何实现它.
yield break当您想要停止返回结果时使用.如果你没有a yield break,编译器会在函数末尾假定一个(就像return;普通函数中的语句一样)
正如Mehrdad所说,它构建了一个状态机.
除了使用Reflector(另一个很好的建议),您可能会发现我关于迭代器块实现的文章很有用.如果它不是用于块,那将是相对简单的finally- 但它们引入了一个额外的复杂性维度!
让我们稍微回顾一下:yield关键字被翻译成许多其他人对状态机所说的。
实际上,这并不完全像使用将在幕后使用的内置实现,而是编译器yield通过实现一个相关接口(包含yield关键字的方法的返回类型)将相关代码重写为状态机。
(有限)状态机只是一段代码,它取决于您在代码中的位置(取决于之前的状态、输入)转到另一个状态操作,这几乎就是您使用和产生时发生的情况方法返回类型为IEnumerator<T>/ IEnumerator。该yield关键字就是要创建另一个行动,从以前的一个,因此状态管理是创建移动到下一个状态MoveNext()的实现。
这正是 C# 编译器 / Roslyn 要做的:检查yield关键字的存在以及包含方法的返回类型的类型,无论是 a IEnumerator<T>, IEnumerable<T>, IEnumeratororIEnumerable然后创建一个反映该方法的私有类,集成必要的变量和状态。
如果您对状态机如何以及编译器如何重写迭代的细节感兴趣,您可以在 Github 上查看这些链接:
Trivia 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...
Run Code Online (Sandbox Code Playgroud)
编译后与上面那段代码相关的变量和类型将如下所示:
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;
Run Code Online (Sandbox Code Playgroud)
关于状态机本身,让我们看一个非常简单的例子,它带有一个虚拟分支,用于产生一些偶数/奇数的东西。
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";
}
}
}
Run Code Online (Sandbox Code Playgroud)
将被翻译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;
}
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,这个实现远非简单,但它完成了工作!
琐事 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();
}
Run Code Online (Sandbox Code Playgroud)
您还可以使用不同的路径/分支以及编译器重写的完整实现来查看此示例。
这是使用SharpLab创建的,您可以使用该工具尝试不同的yield相关执行路径,并查看编译器如何将它们重写为实现中的状态机MoveNext。
关于问题的第二部分,即,yield break已在此处回答
它指定迭代器已经结束。您可以将 yield break 视为不返回值的 return 语句。
| 归档时间: |
|
| 查看次数: |
3584 次 |
| 最近记录: |