ほげほげー

C#メインにプログラミング周りから日常のあれこれとかを不定期に書いていきます。

C# の yield return を少し詳しく解説するよ

とは言えそこまで詳しくないので(ry

yield return の記事ばかりにアクセスが来るので、求められているであろう記事内容を超簡潔にまとめようと思った。

多分 yield return そのものの解説は前回のあれで充分で、追加で多分これが欲しいのではないでしょうか。

実際にどうコンパイルされているのか。

yield return とはなんぞや?

過去記事参照のこと。

C#におけるyieldの挙動 - ほげほげー

読むのダルい人のために簡単な説明。

コンパイル時にIteratorパターンを実装してくれるC#の便利な言語仕様です。

前回の記事では実際の動きに注目して書いていきましたが、
今回はコンパイルした時になにが起きているかの中身に注目して説明を進めます。

yield return がコンパイル時にどのように置き換わるのか?

Iteratorパターンに置き換えられます。

なんでそうなってるってわかるの?

IL読みましょう。自分には(殆ど)読めませんが。*1

実際にコンパイル後のコード確認してみましょう

実際にどのようにコンパイルされているのかを確認するためには、ILSpyを使います。
まずはDLしてきてください。

Redirecting…

次に起動して View > Options を開く。

f:id:tyhe:20140409013304p:plain

Decompiler タブの Decompile enumerators(yield return) のチェックを外します。

おもむろに新規プロジェクトを作成して下記のコードを書いてコンパイルします。

// Hoge.cs
namespace Sample.SeeYield
{
    public class Class1
    {
        public IEnumerable<string> Main()
        {
            yield return "hoge";
        }
    }
}

生成されたdllファイルをILSpyにDrag&Drop

するとこんなコードが展開されます。

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
namespace Sample.SeeYield
{
    public class Class1
    {
        [CompilerGenerated]
        private sealed class <Main>d__0 : IEnumerable<string>, IEnumerable, IEnumerator<string>, IEnumerator, IDisposable
        {
            private string <>2__current;
            private int <>1__state;
            private int <>l__initialThreadId;
            public Class1 <>4__this;
            string IEnumerator<string>.Current
            {
                [DebuggerHidden]
                get
                {
                    return this.<>2__current;
                }
            }
            object IEnumerator.Current
            {
                [DebuggerHidden]
                get
                {
                    return this.<>2__current;
                }
            }
            [DebuggerHidden]
            IEnumerator<string> IEnumerable<string>.GetEnumerator()
            {
                Class1.<Main>d__0 <Main>d__;
                if (Environment.CurrentManagedThreadId == this.<>l__initialThreadId && this.<>1__state == -2)
                {
                    this.<>1__state = 0;
                    <Main>d__ = this;
                }
                else
                {
                    <Main>d__ = new Class1.<Main>d__0(0);
                    <Main>d__.<>4__this = this.<>4__this;
                }
                return <Main>d__;
            }
            [DebuggerHidden]
            IEnumerator IEnumerable.GetEnumerator()
            {
                return this.System.Collections.Generic.IEnumerable<System.String>.GetEnumerator();
            }
            bool IEnumerator.MoveNext()
            {
                bool result;
                switch (this.<>1__state)
                {
                case 0:
                    this.<>1__state = -1;
                    this.<>2__current = "hoge";
                    this.<>1__state = 1;
                    result = true;
                    return result;
                case 1:
                    this.<>1__state = -1;
                    break;
                }
                result = false;
                return result;
            }
            [DebuggerHidden]
            void IEnumerator.Reset()
            {
                throw new NotSupportedException();
            }
            void IDisposable.Dispose()
            {
            }
            [DebuggerHidden]
            public <Main>d__0(int <>1__state)
            {
                this.<>1__state = <>1__state;
                this.<>l__initialThreadId = Environment.CurrentManagedThreadId;
            }
        }
        public IEnumerable<string> Main()
        {
            Class1.<Main>d__0 <Main>d__ = new Class1.<Main>d__0(-2);
            <Main>d__.<>4__this = this;
            return <Main>d__;
        }
    }
}

単純にyield returnしただけで実はこんな複雑なコードが出力されているんです。
元のコードの面影は殆ど無いですね。
裏でこれだけの事をしてくれているからこそ僕らは本質に集中する事ができるのです。感謝感謝。

C#5.0から導入された async await なんかもコンパイル時にはエグい感じに変化しているので
興味がある方は一度 Decompile async methods (async/await) のチェックを外してデコンパイルしてみてください。

こんな感じで今回のお話はお終い。

C#er的には先日Buildで発表されたRoslynとかがキニナリマス。
EIGOコワイのでC#の偉い人がまとめてくれるまで待ちマス。

*1:特にコンパイラがGenerateした所なんてムリムリ