张少帅 發表於 2023-6-8 16:04:00

C# 中的yield return机制和原理

<div id="cnblogs_post_body" class="blogpost-body cnblogs-markdown">
<h2 id="前言">前言<button class="cnblogs-toc-button" title="显示目录导航"></button>#</h2>
<p>&nbsp;&nbsp;&nbsp;&nbsp;当我们编写 C# 代码时,经常需要处理大量的数据集合。在传统的方式中,我们往往需要先将整个数据集合加载到内存中,然后再进行操作。但是如果数据集合非常大,这种方式就会导致内存占用过高,甚至可能导致程序崩溃。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;C# 中的<code>yield return</code>机制可以帮助我们解决这个问题。通过使用<code>yield return</code>,我们可以将数据集合按需生成,而不是一次性生成整个数据集合。这样可以大大减少内存占用,并且提高程序的性能。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;在本文中,我们将深入讨论 C# 中<code>yield return</code>的机制和用法,帮助您更好地理解这个强大的功能,并在实际开发中灵活使用它。</p>
<h2 id="使用方式">使用方式<button class="cnblogs-toc-button" title="显示目录导航"></button>#</h2>
<p>上面我们提到了<code>yield return</code>将数据集合按需生成,而不是一次性生成整个数据集合。接下来通过一个简单的示例,我们看一下它的工作方式是什么样的,以便加深对它的理解</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-keyword">foreach</span> (<span class="hljs-function"><span class="hljs-keyword">var</span> num <span class="hljs-keyword">in</span> <span class="hljs-title">GetInts</span>())</span>
{
    Console.WriteLine(<span class="hljs-string">"外部遍历了:{0}"</span>, num);
}

<span class="hljs-function">IEnumerable&lt;<span class="hljs-built_in">int</span>&gt; <span class="hljs-title">GetInts</span>()</span>
{
    <span class="hljs-keyword">for</span> (<span class="hljs-built_in">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++)
    {
      Console.WriteLine(<span class="hljs-string">"内部遍历了:{0}"</span>, i);
      <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> i;
    }
}
</code></pre>
<p>首先,在<code>GetInts</code>方法中,我们使用<code>yield return</code>关键字来定义一个迭代器。这个迭代器可以按需生成整数序列。在每次循环时,使用<code>yield return</code>返回当前的整数。通过1<code>foreach</code>循环来遍历 <code>GetInts</code>方法返回的整数序列。在迭代时<code>GetInts</code>方法会被执行,但是不会将整个序列加载到内存中。而是在需要时,按需生成序列中的每个元素。在每次迭代时,会输出当前迭代的整数对应的信息。所以输出的结果为</p>
<pre class="highlighter-hljs"><code class="highlighter-hljs hljs language-makefile"><span class="hljs-section">内部遍历了:0</span>
<span class="hljs-section">外部遍历了:0</span>
<span class="hljs-section">内部遍历了:1</span>
<span class="hljs-section">外部遍历了:1</span>
<span class="hljs-section">内部遍历了:2</span>
<span class="hljs-section">外部遍历了:2</span>
<span class="hljs-section">内部遍历了:3</span>
<span class="hljs-section">外部遍历了:3</span>
<span class="hljs-section">内部遍历了:4</span>
<span class="hljs-section">外部遍历了:4</span>
</code></pre>
<p>可以看到,整数序列是按需生成的,并且在每次生成时都会输出相应的信息。这种方式可以大大减少内存占用,并且提高程序的性能。当然从<code>c# 8</code>开始异步迭代的方式同样支持</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-keyword">await</span> <span class="hljs-keyword">foreach</span> (<span class="hljs-function"><span class="hljs-keyword">var</span> num <span class="hljs-keyword">in</span> <span class="hljs-title">GetIntsAsync</span>())</span>
{
    Console.WriteLine(<span class="hljs-string">"外部遍历了:{0}"</span>, num);
}

<span class="hljs-function"><span class="hljs-keyword">async</span> IAsyncEnumerable&lt;<span class="hljs-built_in">int</span>&gt; <span class="hljs-title">GetIntsAsync</span>()</span>
{
    <span class="hljs-keyword">for</span> (<span class="hljs-built_in">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++)
    {
      <span class="hljs-keyword">await</span> Task.Yield();
      Console.WriteLine(<span class="hljs-string">"内部遍历了:{0}"</span>, i);
      <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> i;
    }
}
</code></pre>
<p>和上面不同的是,如果需要用异步的方式,我们需要返回<code>IAsyncEnumerable</code>类型,这种方式的执行结果和上面同步的方式执行的结果是一致的,我们就不做展示了。上面我们的示例都是基于循环持续迭代的,其实使用<code>yield return</code>的方式还可以按需的方式去输出,这种方式适合灵活迭代的方式。如下示例所示</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-keyword">foreach</span> (<span class="hljs-function"><span class="hljs-keyword">var</span> num <span class="hljs-keyword">in</span> <span class="hljs-title">GetInts</span>())</span>
{
    Console.WriteLine(<span class="hljs-string">"外部遍历了:{0}"</span>, num);
}

<span class="hljs-function">IEnumerable&lt;<span class="hljs-built_in">int</span>&gt; <span class="hljs-title">GetInts</span>()</span>
{
    Console.WriteLine(<span class="hljs-string">"内部遍历了:0"</span>);
    <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;

    Console.WriteLine(<span class="hljs-string">"内部遍历了:1"</span>);
    <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;

    Console.WriteLine(<span class="hljs-string">"内部遍历了:2"</span>);
    <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> <span class="hljs-number">2</span>;
}
</code></pre>
<p><code>foreach</code>循环每次会调用<code>GetInts()</code>方法,<code>GetInts()</code>方法的内部便使用<code>yield return</code>关键字返回一个结果。每次遍历都会去执行下一个<code>yield return</code>。所以上面代码输出的结果是</p>
<pre class="highlighter-hljs"><code class="highlighter-hljs hljs language-makefile"><span class="hljs-section">内部遍历了:0</span>
<span class="hljs-section">外部遍历了:0</span>
<span class="hljs-section">内部遍历了:1</span>
<span class="hljs-section">外部遍历了:1</span>
<span class="hljs-section">内部遍历了:2</span>
<span class="hljs-section">外部遍历了:2</span>
</code></pre>
<h2 id="探究本质">探究本质<button class="cnblogs-toc-button" title="显示目录导航"></button>#</h2>
<p>上面我们展示了<code>yield return</code>如何使用的示例,它是一种延迟加载的机制,它可以让我们逐个地处理数据,而不是一次性地将所有数据读取到内存中。接下来我们就来探究一下神奇操作的背后到底是如何实现的,方便让大家更清晰的了解迭代体系相关。</p>
<h4 id="foreach本质">foreach本质<button class="cnblogs-toc-button" title="显示目录导航"></button>#</h4>
<p>首先我们来看一下<code>foreach</code>为什么可以遍历,也就是如果可以被<code>foreach</code>遍历的对象,被遍历的操作需要满足哪些条件,这个时候我们可以反编译工具来看一下编译后的代码是什么样子的,相信大家最熟悉的就是<code>List&lt;T&gt;</code>集合的遍历方式了,那我们就用<code>List&lt;T&gt;</code>的示例来演示一下</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs">List&lt;<span class="hljs-built_in">int</span>&gt; ints = <span class="hljs-keyword">new</span> List&lt;<span class="hljs-built_in">int</span>&gt;();
<span class="hljs-keyword">foreach</span>(<span class="hljs-built_in">int</span> item <span class="hljs-keyword">in</span> ints)
{
    Console.WriteLine(item);
}
</code></pre>
<p>上面的这段代码很简单,我们也没有给它任何初始化的数据,这样可以排除干扰,让我们能更清晰的看到反编译的结果,排除其他干扰。它反编译后的代码是这样的</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs">List&lt;<span class="hljs-built_in">int</span>&gt; list = <span class="hljs-keyword">new</span> List&lt;<span class="hljs-built_in">int</span>&gt;();
List&lt;<span class="hljs-built_in">int</span>&gt;.Enumerator enumerator = list.GetEnumerator();
<span class="hljs-keyword">try</span>
{
    <span class="hljs-keyword">while</span> (enumerator.MoveNext())
    {
      <span class="hljs-built_in">int</span> current = enumerator.Current;
      Console.WriteLine(current);
    }
}
<span class="hljs-keyword">finally</span>
{
    ((IDisposable)enumerator).Dispose();
}
</code></pre>
<blockquote>
<p>可以反编译代码的工具有很多,我用的比较多的一般是<code>ILSpy</code>、<code>dnSpy</code>、<code>dotPeek</code>和在线<code>c#</code>反编译网站sharplab.io,其中<code>dnSpy</code>还可以调试反编译的代码。</p>
</blockquote>
<p>通过上面的反编译之后的代码我们可以看到<code>foreach</code>会被编译成一个固定的结构,也就是我们经常提及的设计模式中的迭代器模式结构</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs">Enumerator enumerator = list.GetEnumerator();
<span class="hljs-keyword">while</span> (enumerator.MoveNext())
{
   <span class="hljs-keyword">var</span> current = enumerator.Current;
}
</code></pre>
<p>通过这段固定的结构我们总结一下<code>foreach</code>的工作原理</p>
<ul>
<li>可以被<code>foreach</code>的对象需要要包含<code>GetEnumerator()</code>方法</li>
<li>迭代器对象包含<code>MoveNext()</code>方法和<code>Current</code>属性</li>
<li><code>MoveNext()</code>方法返回<code>bool</code>类型,判断是否可以继续迭代。<code>Current</code>属性返回当前的迭代结果。</li>
</ul>
<p>我们可以看一下<code>List&lt;T&gt;</code>类可迭代的源码结构是如何实现的</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">List</span>&lt;<span class="hljs-title">T</span>&gt; : <span class="hljs-title">IList</span>&lt;<span class="hljs-title">T</span>&gt;, <span class="hljs-title">IList</span>, <span class="hljs-title">IReadOnlyList</span>&lt;<span class="hljs-title">T</span>&gt;
{
    <span class="hljs-function"><span class="hljs-keyword">public</span> Enumerator <span class="hljs-title">GetEnumerator</span>()</span> =&gt; <span class="hljs-keyword">new</span> Enumerator(<span class="hljs-keyword">this</span>);

    IEnumerator&lt;T&gt; IEnumerable&lt;T&gt;.GetEnumerator() =&gt; Count == <span class="hljs-number">0</span> ? SZGenericArrayEnumerator&lt;T&gt;.Empty : GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() =&gt; ((IEnumerable&lt;T&gt;)<span class="hljs-keyword">this</span>).GetEnumerator();

    <span class="hljs-keyword">public</span> <span class="hljs-keyword">struct</span> Enumerator : IEnumerator&lt;T&gt;, IEnumerator
    {
      <span class="hljs-keyword">public</span> T Current =&gt; _current!;
      <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-built_in">bool</span> <span class="hljs-title">MoveNext</span>()</span>
      {
      }
    }
}
</code></pre>
<p>这里涉及到了两个核心的接口<code>IEnumerable&lt;</code>和<code>IEnumerator</code>,他们两个定义了可以实现迭代的能力抽象,实现方式如下</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IEnumerable</span>
{
    <span class="hljs-function">IEnumerator <span class="hljs-title">GetEnumerator</span>()</span>;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IEnumerator</span>
{
    <span class="hljs-function"><span class="hljs-built_in">bool</span> <span class="hljs-title">MoveNext</span>()</span>;
    <span class="hljs-built_in">object</span> Current{ <span class="hljs-keyword">get</span>; }
    <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">Reset</span>()</span>;
}
</code></pre>
<p>如果类实现<code>IEnumerable</code>接口并实现了<code>GetEnumerator()</code>方法便可以被<code>foreach</code>,迭代的对象是<code>IEnumerator</code>类型,包含一个<code>MoveNext()</code>方法和<code>Current</code>属性。上面的接口是原始对象的方式,这种操作都是针对<code>object</code>类型集合对象。我们实际开发过程中大多数都是使用的泛型集合,当然也有对应的实现方式,如下所示</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IEnumerable</span>&lt;<span class="hljs-keyword">out</span> <span class="hljs-title">T</span>&gt; : <span class="hljs-title">IEnumerable</span>
{
    <span class="hljs-keyword">new</span> <span class="hljs-function">IEnumerator&lt;T&gt; <span class="hljs-title">GetEnumerator</span>()</span>;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IEnumerator</span>&lt;<span class="hljs-keyword">out</span> <span class="hljs-title">T</span>&gt; : <span class="hljs-title">IDisposable</span>, <span class="hljs-title">IEnumerator</span>
{
    <span class="hljs-keyword">new</span> T Current{ <span class="hljs-keyword">get</span>; }
}
</code></pre>
<blockquote>
<p>可以被<code>foreach</code>迭代并不意味着一定要去实现<code>IEnumerable</code>接口,这只是给我们提供了一个可以被迭代的抽象的能力。只要类中包含<code>GetEnumerator()</code>方法并返回一个迭代器,迭代器里包含返回<code>bool</code>类型的<code>MoveNext()</code>方法和获取当前迭代对象的<code>Current</code>属性即可。</p>
</blockquote>
<h4 id="yield-return本质">yield return本质<button class="cnblogs-toc-button" title="显示目录导航"></button>#</h4>
<p>上面我们看到了可以被<code>foreach</code>迭代的本质是什么,那么<code>yield return</code>的返回值可以被<code>IEnumerable&lt;T&gt;</code>接收说明其中必有蹊跷,我们反编译一下我们上面的示例看一下反编译之后代码,为了方便大家对比反编译结果,这里我把上面的示例再次粘贴一下</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-keyword">foreach</span> (<span class="hljs-function"><span class="hljs-keyword">var</span> num <span class="hljs-keyword">in</span> <span class="hljs-title">GetInts</span>())</span>
{
    Console.WriteLine(<span class="hljs-string">"外部遍历了:{0}"</span>, num);
}

<span class="hljs-function">IEnumerable&lt;<span class="hljs-built_in">int</span>&gt; <span class="hljs-title">GetInts</span>()</span>
{
    <span class="hljs-keyword">for</span> (<span class="hljs-built_in">int</span> i = <span class="hljs-number">0</span>; i &lt; <span class="hljs-number">5</span>; i++)
    {
      Console.WriteLine(<span class="hljs-string">"内部遍历了:{0}"</span>, i);
      <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> i;
    }
}
</code></pre>
<p>它的反编译结果,这里咱们就不全部展示了,只展示一下核心的逻辑</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-comment">//foeach编译后的结果</span>
IEnumerator&lt;<span class="hljs-built_in">int</span>&gt; enumerator = GetInts().GetEnumerator();
<span class="hljs-keyword">try</span>
{
    <span class="hljs-keyword">while</span> (enumerator.MoveNext())
    {
      <span class="hljs-built_in">int</span> current = enumerator.Current;
      Console.WriteLine(<span class="hljs-string">"外部遍历了:{0}"</span>, current);
    }
}
<span class="hljs-keyword">finally</span>
{
    <span class="hljs-keyword">if</span> (enumerator != <span class="hljs-literal">null</span>)
    {
      enumerator.Dispose();
    }
}

<span class="hljs-comment">//GetInts方法编译后的结果</span>
<span class="hljs-function"><span class="hljs-keyword">private</span> IEnumerable&lt;<span class="hljs-built_in">int</span>&gt; <span class="hljs-title">GetInts</span>()</span>
{
    &lt;GetInts&gt;d__1 &lt;GetInts&gt;d__ = <span class="hljs-keyword">new</span> &lt;GetInts&gt;d__1(<span class="hljs-number">-2</span>);
    &lt;GetInts&gt;d__.&lt;&gt;<span class="hljs-number">4</span>__this = <span class="hljs-keyword">this</span>;
    <span class="hljs-keyword">return</span> &lt;GetInts&gt;d__;
}
</code></pre>
<p>这里我们可以看到<code>GetInts()</code>方法里原来的代码不见了,而是多了一个<code>&lt;GetInts&gt;d__1 </code>l类型,也就是说<code>yield return</code>本质是<code>语法糖</code>。我们看一下<code>&lt;GetInts&gt;d__1</code>类的实现</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-comment">//生成的类即实现了IEnumerable接口也实现了IEnumerator接口</span>
<span class="hljs-comment">//说明它既包含了GetEnumerator()方法,也包含MoveNext()方法和Current属性</span>
<span class="hljs-keyword">private</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> &lt;&gt;<span class="hljs-title">GetIntsd__1</span> : <span class="hljs-title">IEnumerable</span>&lt;<span class="hljs-title">int</span>&gt;, <span class="hljs-title">IEnumerable</span>, <span class="hljs-title">IEnumerator</span>&lt;<span class="hljs-title">int</span>&gt;, <span class="hljs-title">IEnumerator</span>, <span class="hljs-title">IDisposable</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-built_in">int</span> &lt;&gt;<span class="hljs-number">1</span>__state;
    <span class="hljs-comment">//当前迭代结果</span>
    <span class="hljs-keyword">private</span> <span class="hljs-built_in">int</span> &lt;&gt;<span class="hljs-number">2</span>__current;
    <span class="hljs-keyword">private</span> <span class="hljs-built_in">int</span> &lt;&gt;l__initialThreadId;
    <span class="hljs-keyword">public</span> C &lt;&gt;<span class="hljs-number">4</span>__this;
    <span class="hljs-keyword">private</span> <span class="hljs-built_in">int</span> &lt;i&gt;<span class="hljs-number">5</span>__1;

    <span class="hljs-comment">//当前迭代到的结果</span>
    <span class="hljs-built_in">int</span> IEnumerator&lt;<span class="hljs-built_in">int</span>&gt;.Current
    {
      <span class="hljs-keyword">get</span>{ <span class="hljs-keyword">return</span> &lt;&gt;<span class="hljs-number">2</span>__current; }
    }

    <span class="hljs-comment">//当前迭代到的结果</span>
    <span class="hljs-built_in">object</span> IEnumerator.Current
    {
      <span class="hljs-keyword">get</span>{ <span class="hljs-keyword">return</span> &lt;&gt;<span class="hljs-number">2</span>__current; }
    }

    <span class="hljs-comment">//构造函数包含状态字段,变向说明靠状态机去实现核心流程流转</span>
    <span class="hljs-keyword">public</span> &lt;GetInts&gt;d__1(<span class="hljs-built_in">int</span> &lt;&gt;<span class="hljs-number">1</span>__state)
    {
      <span class="hljs-keyword">this</span>.&lt;&gt;<span class="hljs-number">1</span>__state = &lt;&gt;<span class="hljs-number">1</span>__state;
      &lt;&gt;l__initialThreadId = Environment.CurrentManagedThreadId;
    }

    <span class="hljs-comment">//核心方法MoveNext</span>
    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-built_in">bool</span> <span class="hljs-title">MoveNext</span>()</span>
    {
      <span class="hljs-built_in">int</span> num = &lt;&gt;<span class="hljs-number">1</span>__state;
      <span class="hljs-keyword">if</span> (num != <span class="hljs-number">0</span>)
      {
            <span class="hljs-keyword">if</span> (num != <span class="hljs-number">1</span>)
            {
                <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
            }
            <span class="hljs-comment">//控制状态</span>
            &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">-1</span>;
            <span class="hljs-comment">//自增 也就是代码里循环的i++</span>
            &lt;i&gt;<span class="hljs-number">5</span>__1++;
      }
      <span class="hljs-keyword">else</span>
      {
            &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">-1</span>;
            &lt;i&gt;<span class="hljs-number">5</span>__1 = <span class="hljs-number">0</span>;
      }
      <span class="hljs-comment">//循环终止条件 上面循环里的i&lt;5</span>
      <span class="hljs-keyword">if</span> (&lt;i&gt;<span class="hljs-number">5</span>__1 &lt; <span class="hljs-number">5</span>)
      {
            Console.WriteLine(<span class="hljs-string">"内部遍历了:{0}"</span>, &lt;i&gt;<span class="hljs-number">5</span>__1);
            <span class="hljs-comment">//把当前迭代结果赋值给Current属性</span>
            &lt;&gt;<span class="hljs-number">2</span>__current = &lt;i&gt;<span class="hljs-number">5</span>__1;
            &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">1</span>;
            <span class="hljs-comment">//说明可以继续迭代</span>
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
      }
      <span class="hljs-comment">//迭代结束</span>
      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }

    <span class="hljs-comment">//IEnumerator的MoveNext方法</span>
    <span class="hljs-built_in">bool</span> IEnumerator.MoveNext()
    {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.MoveNext();
    }

    <span class="hljs-comment">//IEnumerable的IEnumerable方法</span>
    IEnumerator&lt;<span class="hljs-built_in">int</span>&gt; IEnumerable&lt;<span class="hljs-built_in">int</span>&gt;.IEnumerable()
    {
      <span class="hljs-comment">//实例化&lt;GetInts&gt;d__1实例</span>
      &lt;GetInts&gt;d__1 &lt;GetInts&gt;d__;
      <span class="hljs-keyword">if</span> (&lt;&gt;<span class="hljs-number">1</span>__state == <span class="hljs-number">-2</span> &amp;&amp; &lt;&gt;l__initialThreadId == Environment.CurrentManagedThreadId)
      {
            &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">0</span>;
            &lt;GetInts&gt;d__ = <span class="hljs-keyword">this</span>;
      }
      <span class="hljs-keyword">else</span>
      {
            <span class="hljs-comment">//给状态机初始化</span>
            &lt;GetInts&gt;d__ = <span class="hljs-keyword">new</span> &lt;GetInts&gt;d__1(<span class="hljs-number">0</span>);
            &lt;GetInts&gt;d__.&lt;&gt;<span class="hljs-number">4</span>__this = &lt;&gt;<span class="hljs-number">4</span>__this;
      }
      <span class="hljs-comment">//因为&lt;GetInts&gt;d__1实现了IEnumerator接口所以可以直接返回</span>
      <span class="hljs-keyword">return</span> &lt;GetInts&gt;d__;
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
      <span class="hljs-comment">//因为&lt;GetInts&gt;d__1实现了IEnumerator接口所以可以直接转换</span>
      <span class="hljs-keyword">return</span> ((IEnumerable&lt;<span class="hljs-built_in">int</span>&gt;)<span class="hljs-keyword">this</span>).GetEnumerator();
    }

    <span class="hljs-keyword">void</span> IEnumerator.Reset()
    {
    }

    <span class="hljs-keyword">void</span> IDisposable.Dispose()
    {
    }
}
</code></pre>
<p>通过它生成的类我们可以看到,该类即实现了<code>IEnumerable</code>接口也实现了<code>IEnumerator</code>接口说明它既包含了<code>GetEnumerator()</code>方法,也包含<code>MoveNext()</code>方法和<code>Current</code>属性。用这一个类就可以满足可被<code>foeach</code>迭代的核心结构。我们手动写的<code>for</code>代码被包含到了<code>MoveNext()</code>方法里,它包含了定义的状态机制代码,并且根据当前的状态机代码将迭代移动到下一个元素。我们大概讲解一下我们的<code>for</code>代码被翻译到<code>MoveNext()</code>方法里的执行流程</p>
<ul>
<li>首次迭代时<code>&lt;&gt;1__state</code>被初始化成0,代表首个被迭代的元素,这个时候<code>Current</code>初始值为0,循环控制变量<code>&lt;i&gt;5__1</code>初始值也为0。</li>
<li>判断是否满足终止条件,不满足则执行循环里的逻辑。并更改装填机<code>&lt;&gt;1__state</code>为1,代表首次迭代执行完成。</li>
<li>循环控制变量<code>&lt;i&gt;5__1</code>继续自增并更改并更改装填机<code>&lt;&gt;1__state</code>为-1,代表可持续迭代。并循环执行循环体的自定义逻辑。</li>
<li>不满足迭代条件则返回<code>false</code>,也就是代表了<code>MoveNext()</code>以不满足迭代条件<code>while (enumerator.MoveNext())</code>逻辑终止。</li>
</ul>
<p>上面我们还展示了另一种<code>yield return</code>的方式,就是同一个方法里包含多个<code>yield return</code>的形式</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-function">IEnumerable&lt;<span class="hljs-built_in">int</span>&gt; <span class="hljs-title">GetInts</span>()</span>
{
    Console.WriteLine(<span class="hljs-string">"内部遍历了:0"</span>);
    <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;

    Console.WriteLine(<span class="hljs-string">"内部遍历了:1"</span>);
    <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;

    Console.WriteLine(<span class="hljs-string">"内部遍历了:2"</span>);
    <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> <span class="hljs-number">2</span>;
}
</code></pre>
<p>上面这段代码反编译的结果如下所示,这里咱们只展示核心的方法<code>MoveNext()</code>的实现</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-built_in">bool</span> <span class="hljs-title">MoveNext</span>()</span>
{
    <span class="hljs-keyword">switch</span> (&lt;&gt;<span class="hljs-number">1</span>__state)
    {
      <span class="hljs-literal">default</span>:
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-number">0</span>:
            &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">-1</span>;
            Console.WriteLine(<span class="hljs-string">"内部遍历了:0"</span>);
            &lt;&gt;<span class="hljs-number">2</span>__current = <span class="hljs-number">0</span>;
            &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">1</span>;
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-number">1</span>:
            &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">-1</span>;
            Console.WriteLine(<span class="hljs-string">"内部遍历了:1"</span>);
            &lt;&gt;<span class="hljs-number">2</span>__current = <span class="hljs-number">1</span>;
            &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">2</span>;
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-number">2</span>:
            &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">-1</span>;
            Console.WriteLine(<span class="hljs-string">"内部遍历了:2"</span>);
            &lt;&gt;<span class="hljs-number">2</span>__current = <span class="hljs-number">2</span>;
            &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">3</span>;
            <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
      <span class="hljs-keyword">case</span> <span class="hljs-number">3</span>:
            &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">-1</span>;
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }
}
</code></pre>
<p>通过编译后的代码我们可以看到,多个<code>yield return</code>的形式会被编译成<code>switch...case</code>的形式,有几个<code>yield return</code>则会编译成<code>n+1</code>个<code>case</code>,多出来的一个<code>case</code>则代表的<code>MoveNext()</code>终止条件,也就是返回<code>false</code>的条件。其它的<code>case</code>则返回<code>true</code>表示可以继续迭代。</p>
<h4 id="iasyncenumerable接口">IAsyncEnumerable接口<button class="cnblogs-toc-button" title="显示目录导航"></button>#</h4>
<p>上面我们展示了同步<code>yield return</code>方式,<code>c# 8</code>开始新增了<code>IAsyncEnumerable&lt;T&gt;</code>接口,用于完成异步迭代,也就是迭代器逻辑里包含异步逻辑的场景。<code>IAsyncEnumerable&lt;T&gt;</code>接口的实现代码如下所示</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IAsyncEnumerable</span>&lt;<span class="hljs-keyword">out</span> <span class="hljs-title">T</span>&gt;
{
    <span class="hljs-function">IAsyncEnumerator&lt;T&gt; <span class="hljs-title">GetAsyncEnumerator</span>(<span class="hljs-params">CancellationToken cancellationToken = <span class="hljs-literal">default</span></span>)</span>;
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IAsyncEnumerator</span>&lt;<span class="hljs-keyword">out</span> <span class="hljs-title">T</span>&gt; : <span class="hljs-title">IAsyncDisposable</span>
{
    <span class="hljs-function">ValueTask&lt;<span class="hljs-built_in">bool</span>&gt; <span class="hljs-title">MoveNextAsync</span>()</span>;
    T Current { <span class="hljs-keyword">get</span>; }
}
</code></pre>
<p>它最大的不同则是同步的<code>IEnumerator</code>包含的是<code>MoveNext()</code>方法返回的是<code>bool</code>,<code>IAsyncEnumerator</code>接口包含的是<code>MoveNextAsync()</code>异步方法,返回的是<code>ValueTask&lt;bool&gt;</code>类型。所以上面的示例代码</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-keyword">await</span> <span class="hljs-keyword">foreach</span> (<span class="hljs-function"><span class="hljs-keyword">var</span> num <span class="hljs-keyword">in</span> <span class="hljs-title">GetIntsAsync</span>())</span>
{
    Console.WriteLine(<span class="hljs-string">"外部遍历了:{0}"</span>, num);
}
</code></pre>
<p>所以这里的<code>await</code>虽然是加在<code>foreach</code>上面,但是实际作用的则是每一次迭代执行的<code>MoveNextAsync()</code>方法。可以大致理解为下面的工作方式</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs">IAsyncEnumerator&lt;<span class="hljs-built_in">int</span>&gt; enumerator = list.GetAsyncEnumerator();
<span class="hljs-keyword">while</span> (enumerator.MoveNextAsync().GetAwaiter().GetResult())
{
   <span class="hljs-keyword">var</span> current = enumerator.Current;
}
</code></pre>
<p>当然,实际编译成的代码并不是这个样子的,我们在之前的文章&lt;研究c#异步操作async await状态机的总结&gt;一文中讲解过<code>async await</code>会被编译成<code>IAsyncStateMachine</code>异步状态机,所以<code>IAsyncEnumerator&lt;T&gt;</code>结合<code>yield return</code>的实现比同步的方式更加复杂而且包含更多的代码,不过实现原理可以结合同步的方式类比一下,但是要同时了解异步状态机的实现,这里咱们就不过多展示<code>异步yield return</code>的编译后实现了,有兴趣的同学可以自行了解一下。</p>
<h4 id="foreach增强">foreach增强<button class="cnblogs-toc-button" title="显示目录导航"></button>#</h4>
<p><code>c# 9</code>增加了对foreach的增强的功能,即通过扩展方法的形式,对原本具备包含<code>foreach</code>能力的对象增加<code>GetEnumerator()</code>方法,使得普通类在不具备<code>foreach</code>的能力的情况下也可以使用来迭代。它的使用方式如下</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs">Foo foo = <span class="hljs-keyword">new</span> Foo();
<span class="hljs-keyword">foreach</span> (<span class="hljs-built_in">int</span> item <span class="hljs-keyword">in</span> foo)
{
    Console.WriteLine(item);
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Foo</span>
{
    <span class="hljs-keyword">public</span> List&lt;<span class="hljs-built_in">int</span>&gt; Ints { <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; } = <span class="hljs-keyword">new</span> List&lt;<span class="hljs-built_in">int</span>&gt;();
}

<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title">Bar</span>
{
    <span class="hljs-comment">//给Foo定义扩展方法</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IEnumerator&lt;<span class="hljs-built_in">int</span>&gt; <span class="hljs-title">GetEnumerator</span>(<span class="hljs-params"><span class="hljs-keyword">this</span> Foo foo</span>)</span>
    {
      <span class="hljs-keyword">foreach</span> (<span class="hljs-built_in">int</span> item <span class="hljs-keyword">in</span> foo.Ints)
      {
            <span class="hljs-keyword">yield</span> <span class="hljs-keyword">return</span> item;
      }
    }
}
</code></pre>
<p>这个功能确实比较强大,满足开放封闭原则,我们可以在不修改原始代码的情况,增强代码的功能,可以说是非常的实用。我们来看一下它的编译后的结果是啥</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs">Foo foo = <span class="hljs-keyword">new</span> Foo();
IEnumerator&lt;<span class="hljs-built_in">int</span>&gt; enumerator = Bar.GetEnumerator(foo);
<span class="hljs-keyword">try</span>
{
    <span class="hljs-keyword">while</span> (enumerator.MoveNext())
    {
      <span class="hljs-built_in">int</span> current = enumerator.Current;
      Console.WriteLine(current);
    }
}
<span class="hljs-keyword">finally</span>
{
    <span class="hljs-keyword">if</span> (enumerator != <span class="hljs-literal">null</span>)
    {
      enumerator.Dispose();
    }
}
</code></pre>
<p>这里我们看到扩展方法<code>GetEnumerator()</code>本质也是语法糖,会把扩展能力编译成<code>扩展类.GetEnumerator(被扩展实例)</code>的方式。也就是我们写代码时候的原始方式,只是编译器帮我们生成了它的调用方式。接下来我们看一下<code>GetEnumerator()</code>扩展方法编译成了什么</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> IEnumerator&lt;<span class="hljs-built_in">int</span>&gt; <span class="hljs-title">GetEnumerator</span>(<span class="hljs-params">Foo foo</span>)</span>
{
    &lt;GetEnumerator&gt;d__0 &lt;GetEnumerator&gt;d__ = <span class="hljs-keyword">new</span> &lt;GetEnumerator&gt;d__0(<span class="hljs-number">0</span>);
    &lt;GetEnumerator&gt;d__.foo = foo;
    <span class="hljs-keyword">return</span> &lt;GetEnumerator&gt;d__;
}
</code></pre>
<p>看到这个代码是不是觉得很眼熟了,不错和上面<code>yield return本质</code>这一节里讲到的语法糖生成方式是一样的了,同样的编译时候也是生成了一个对应类,这里的类是<code>&lt;GetEnumerator&gt;d__0</code>,我们看一下该类的结构</p>
<pre class="highlighter-hljs"><code class="language-csharp highlighter-hljs hljs"><span class="hljs-keyword">private</span> <span class="hljs-keyword">sealed</span> <span class="hljs-keyword">class</span> &lt;<span class="hljs-title">GetEnumerator</span>&gt;<span class="hljs-title">d__0</span> : <span class="hljs-title">IEnumerator</span>&lt;<span class="hljs-title">int</span>&gt;, <span class="hljs-title">IEnumerator</span>, <span class="hljs-title">IDisposable</span>
{
    <span class="hljs-keyword">private</span> <span class="hljs-built_in">int</span> &lt;&gt;<span class="hljs-number">1</span>__state;
    <span class="hljs-keyword">private</span> <span class="hljs-built_in">int</span> &lt;&gt;<span class="hljs-number">2</span>__current;
    <span class="hljs-keyword">public</span> Foo foo;
    <span class="hljs-keyword">private</span> List&lt;<span class="hljs-built_in">int</span>&gt;.Enumerator &lt;&gt;s__1;
    <span class="hljs-keyword">private</span> <span class="hljs-built_in">int</span> &lt;item&gt;<span class="hljs-number">5</span>__2;

    <span class="hljs-built_in">int</span> IEnumerator&lt;<span class="hljs-built_in">int</span>&gt;.Current
    {
      <span class="hljs-keyword">get</span>{ <span class="hljs-keyword">return</span> &lt;&gt;<span class="hljs-number">2</span>__current; }
    }

    <span class="hljs-built_in">object</span> IEnumerator.Current
    {
      <span class="hljs-keyword">get</span>{ <span class="hljs-keyword">return</span> &lt;&gt;<span class="hljs-number">2</span>__current; }
    }

    <span class="hljs-keyword">public</span> &lt;GetEnumerator&gt;d__0(<span class="hljs-built_in">int</span> &lt;&gt;<span class="hljs-number">1</span>__state)
    {
      <span class="hljs-keyword">this</span>.&lt;&gt;<span class="hljs-number">1</span>__state = &lt;&gt;<span class="hljs-number">1</span>__state;
    }

    <span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-built_in">bool</span> <span class="hljs-title">MoveNext</span>()</span>
    {
      <span class="hljs-keyword">try</span>
      {
            <span class="hljs-built_in">int</span> num = &lt;&gt;<span class="hljs-number">1</span>__state;
            <span class="hljs-keyword">if</span> (num != <span class="hljs-number">0</span>)
            {
                <span class="hljs-keyword">if</span> (num != <span class="hljs-number">1</span>)
                {
                  <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
                }
                &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">-3</span>;
            }
            <span class="hljs-keyword">else</span>
            {
                &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">-1</span>;
                <span class="hljs-comment">//因为示例中的Ints我们使用的是List&lt;T&gt;</span>
                &lt;&gt;s__1 = foo.Ints.GetEnumerator();
                &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">-3</span>;
            }
            <span class="hljs-comment">//因为上面的扩展方法里使用的是foreach遍历方式</span>
            <span class="hljs-comment">//这里也被编译成了实际生产方式</span>
            <span class="hljs-keyword">if</span> (&lt;&gt;s__1.MoveNext())
            {
                &lt;item&gt;<span class="hljs-number">5</span>__2 = &lt;&gt;s__1.Current;
                &lt;&gt;<span class="hljs-number">2</span>__current = &lt;item&gt;<span class="hljs-number">5</span>__2;
                &lt;&gt;<span class="hljs-number">1</span>__state = <span class="hljs-number">1</span>;
                <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
            }
            &lt;&gt;m__Finally1();
            &lt;&gt;s__1 = <span class="hljs-literal">default</span>(List&lt;<span class="hljs-built_in">int</span>&gt;.Enumerator);
            <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
      }
      <span class="hljs-keyword">catch</span>
      {
            ((IDisposable)<span class="hljs-keyword">this</span>).Dispose();
            <span class="hljs-keyword">throw</span>;
      }
    }

    <span class="hljs-built_in">bool</span> IEnumerator.MoveNext()
    {
      <span class="hljs-keyword">return</span> <span class="hljs-keyword">this</span>.MoveNext();
    }

    <span class="hljs-keyword">void</span> IDisposable.Dispose()
    {
    }

    <span class="hljs-keyword">void</span> IEnumerator.Reset()
    {
    }

    <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> &lt;&gt;m__Finally1()
    {
    }
}
</code></pre>
<p>看到编译器生成的代码,我们可以看到<code>yield return</code>生成的代码结构都是一样的,只是<code>MoveNext()</code>里的逻辑取决于我们写代码时候的具体逻辑,不同的逻辑生成不同的代码。这里咱们就不在讲解它生成的代码了,因为和上面咱们讲解的代码逻辑是差不多的。</p>
<h2 id="总结">总结<button class="cnblogs-toc-button" title="显示目录导航"></button>#</h2>
<p>&nbsp;&nbsp;&nbsp;&nbsp;通过本文我们介绍了<code>c#</code>中的<code>yield return</code>语法,并探讨了由它带来的一些思考。我们通过一些简单的例子,展示了<code>yield return</code>的使用方式,知道了迭代器来是如何按需处理大量数据。同时,我们通过分析<code>foreach</code>迭代和<code>yield return</code>语法的本质,讲解了它们的实现原理和底层机制。好在涉及到的知识整体比较简单,仔细阅读相关实现代码的话相信会了解背后的实现原理,这里就不过多赘述了。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;当你遇到挑战和困难时,请不要轻易放弃。无论你面对的是什么,只要你肯努力去尝试,去探索,去追求,你一定能够克服困难,走向成功。记住,成功不是一蹴而就的,它需要我们不断努力和坚持。相信自己,相信自己的能力,相信自己的潜力,你一定能够成为更好的自己。</p>
</div>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>出处:https://www.cnblogs.com/wucy/p/17443749.html</p>

</div>
<div id="MySignature" role="contentinfo">
    <div class="div_masklayer" id="div_masklayer"></div>
<div class="div_popup" id="Div_popup">
<div style="float:left;width:50%">
<img class="img_zfb" id="img_zfb" src="https://images.cnblogs.com/cnblogs_com/mq0036/508398/o_12.png" />
</div>
<div style="float:left;width:50%">
<img class="img_zfb" id="img_zfb" src="https://images.cnblogs.com/cnblogs_com/mq0036/508398/o_200921131119o_12.png">
</div>
<p class="mid">您的资助是我最大的动力!<br>金额随意,欢迎来赏!<br>
<span style="color: #f9f">付</span>款后有任何问题请给我留言。</p>
</div>


<div class="autograph">
    <p> <span style="display: none"> 如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【<strong>推荐</strong>】按钮。<br>
      </span> 如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的【<strong>关注我</strong>】。(●'◡'●)</p>
    <p>因为,我的写作热情也离不开您的肯定与支持,感谢您的阅读,我是【<strong>Jack_孟</strong>】!</p>
    <div class="blogds">
      <b style="color:#f00;font-size: 22px">如果对你有所帮助,赞助一杯咖啡!打</b>
      
      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
      <b style="color: #f00; font-size: 22px">付款后有任何问题请给我留言!!!</b>
    </div>
    <p>本文来自博客园,作者:jack_Meng,转载请注明原文链接:https://www.cnblogs.com/mq0036/p/17466792.html</p>
    <p>【免责声明】本文来自源于网络,如涉及版权或侵权问题,请及时联系我们,我们将第一时间删除或更改!</p>
</div><br><br>
来源:https://www.cnblogs.com/mq0036/p/17466792.html
頁: [1]
查看完整版本: C# 中的yield return机制和原理