yield函数c语言,关于C#:yield语句实现

我想以一种易于理解的形式了解有关yield声明的所有信息。

我已经阅读过yield语句及其在实现迭代器模式时的易用性。然而,大部分是非常干燥的。我想了解一下微软是如何处理收益率的。

另外,你什么时候使用屈服点?

你不能同时得到"一切"和"容易理解",对不起!仅供参考,我将于7月2日从语言设计和实现的角度发表一系列关于这个特性的博客文章。

以下是从陈瑞蒙的博客开始:

C语言中迭代器的实现及其后果(第1部分)

C语言中迭代器的实现及其后果(第2部分)

C语言中迭代器的实现及其后果(第3部分)

yield通过在内部构建一个状态机来工作。当例程退出并下次从该状态恢复时,它存储该例程的当前状态。

您可以使用Reflector来查看编译器是如何实现它的。

当您想停止返回结果时,使用yield break。如果没有yield break语句,编译器会在函数末尾假定一个(就像正常函数中的return;语句一样)。

它是什么意思"程序的当前状态":处理器寄存器值,帧指针等?

看看连体衣

@tcraft-microsoft的规范实现不使用不同的堆栈/分段的堆栈等。它们使用堆分配的对象来存储状态。

正如梅尔达所说,它建立了一个状态机。

除了使用Reflector(另一个很好的建议),您可能会发现我关于迭代器块实现的文章很有用。如果没有finally块,这将相对简单,但它们引入了一个额外的复杂度维度!

让我们倒回去一点:yield关键字的翻译和许多其他人说的对状态机的翻译一样。

实际上,这并不完全像使用将在后台使用的内置实现,而是编译器通过实现一个相关接口(包含yield关键字的方法的返回类型)将与yield相关的代码重写到状态机。

(有限)状态机只是一段代码,根据您在代码中的位置(取决于前一个状态,输入)转到另一个状态操作,当您使用和生成方法返回类型为IEnumerator/IEnumerator时,会发生这种情况。yield关键字将创建另一个操作,从上一个状态移到下一个状态,因此在MoveNext()实现中创建状态管理。

这正是C编译器/Roslyn要做的:检查是否存在yield关键字以及包含方法的返回类型,是否是IEnumerator、IEnumerable、IEnumerator或IEnumerable,然后创建反映该方法的私有类,集成必要的变量和状态。

如果您对状态机以及编译器如何重写迭代的细节感兴趣,可以在GitHub上查看这些链接:

IteratorRewriter源代码

StateMachineRewriter:上述源代码的父类

小贴士1:AsyncRewriter(在编写async/await代码时使用)也继承自StateMachineRewriter,因为它还利用了后面的状态机。

如前所述,状态机在bool MoveNext()生成的实现中有很强的反映,其中有一个switch,有时是一些老式的goto,它基于一个状态字段,表示方法中不同状态的执行路径。

编译器从用户代码生成的代码看起来不太"好",主要是因为编译器在这里和那里添加了一些奇怪的前缀和后缀。

例如,代码:

public class TestClass

{

private int _iAmAHere = 0;

public IEnumerator 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 d__1 : IEnumerator, IDisposable, IEnumerator

{

// Always present

private int <>1__state;

private int <>2__current;

// Containing class

public TestClass <>4__this;

private int 5__1;

private int 5__2;

private int 5__3;

private int 5__4;

private int 5__5;

关于状态机本身,让我们来看一个非常简单的例子,其中有一个用于生成一些偶数/奇数的伪分支。

public class Example

{

public IEnumerator 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;

5__1 = 1;

5__2 = 42;

5__3 = 5__1;

break;

case 1:

<>1__state = -1;

goto IL_0094;

case 2:

{

<>1__state = -1;

goto IL_0094;

}

IL_0094:

5__3++;

break;

}

if (5__3 < 5__2)

{

if (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方法返回类型发生了什么?那么,它将生成一个实现IEnumerator的类,而不是生成一个实现IEnumerable和IEnumerator的类,这样IEnumeratorGetEnumerator()的实现将利用相同生成的类。

关于使用yield关键字时自动实现的几个接口的温馨提示:

public interface IEnumerable : IEnumerable

{

new IEnumerator GetEnumerator();

}

public interface IEnumerator : IDisposable, IEnumerator

{

T Current { get; }

}

public interface IEnumerator

{

bool MoveNext();

object Current { get; }

void Reset();

}

您还可以使用不同的路径/分支和编译器重写的完整实现来检查这个示例。

这是使用sharplab创建的,您可以使用该工具尝试不同的yield相关的执行路径,并查看编译器将如何在MoveNext实现中将它们重写为状态机。

关于问题的第二部分,即yield break,这里已经回答了。

It specifies that an iterator has come to an end. You can think of

yield break as a return statement which does not return a value.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值