【问题标题】:yield statement implementationyield 语句实现
【发布时间】:2010-10-19 01:19:03
【问题描述】:

我想以易于理解的形式了解yield 语句的所有内容

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

另外,你什么时候使用yield break?

【问题讨论】:

    标签: c# .net yield iterator


    【解决方案1】:

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

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

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

    【讨论】:

    • “例程的当前状态”是什么意思:处理器寄存器值、帧指针等?
    • 看看coroutines
    • @Tcraft Microsoft 的规范实现不使用不同的堆栈/分段堆栈/等。他们使用堆分配的对象来存储状态。
    【解决方案2】:

    正如 Mehrdad 所说,它构建了一个状态机。

    除了使用 Reflector(另一个很好的建议)之外,您可能会发现 my article on iterator block implementation 很有用。如果不是 finally 块,它会相对简单 - 但它们引入了一个完整的额外维度!

    【讨论】:

      【解决方案3】:

      让我们稍微回顾一下:yield 关键字被翻译成许多其他人对状态机所说的那样。

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

      A(有限)state machine 只是一段代码,取决于您在代码中的位置(取决于先前的状态,输入)转到另一个状态操作,这几乎就是您在正在使用方法返回类型为 IEnumerator<T> / IEnumerator 的 yield。 yield 关键字将创建另一个动作以从前一个状态移动到下一个状态,因此状态管理是在 MoveNext() 实现中创建的。

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

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

      琐事 1AsyncRewriter(在编写 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;
      } 
      

      正如您所见,这个实现远非简单,但它确实可以完成工作!

      琐事 2IEnumerable / IEnumerable&lt;T&gt; 方法返回类型会发生什么?
      好吧,它不仅会生成一个实现IEnumerator&lt;T&gt; 的类,它还会生成一个同时实现IEnumerable&lt;T&gt;IEnumerator&lt;T&gt; 的类,以便IEnumerator&lt;T&gt; 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 语句。

      【讨论】:

        猜你喜欢
        • 2010-09-13
        • 2020-04-07
        • 2022-01-01
        • 2018-01-01
        • 2011-02-22
        • 2013-12-07
        • 1970-01-01
        • 2011-02-20
        相关资源
        最近更新 更多