C#で使用されるyieldキーワードは何ですか?


828

IList <>のフラグメントのみを公開するにはどうすればよいですか」の質問の回答の1つに、次のコードスニペットが含まれていました。

IEnumerable<object> FilteredList()
{
    foreach(object item in FullList)
    {
        if(IsItemInPartialList(item))
            yield return item;
    }
}

そこではyieldキーワードは何をしますか?私はそれがいくつかの場所で参照されているのを見ました、そしてもう一つの質問ですが、私はそれが実際に何をしているのか完全には理解していません。あるスレッドが別のスレッドに譲歩するという意味で、私は歩留まりについて考えることに慣れていますが、それはここでは関係がないようです。


それに関するMSDNリンクだけがここにありますmsdn.microsoft.com/en-us/library/vstudio/9k7k7cf0.aspx
開発者

14
これは当然のことです。混乱は、「戻り値」が関数の出力として表示されるように条件付けられている一方で、「戻り値」が前に表示されていないことに起因しています。
ラリー

4
私はドキュメントを読みましたが、私はまだ理解していません。(
Ortund

回答:


737

yieldキーワードは、実際にはここに非常に多くありません。

関数は、IEnumerable<object>インターフェイスを実装するオブジェクトを返します。呼び出し元の関数がforeachこのオブジェクトに対して呼び出しを開始すると、その関数は、「生成」されるまで再度呼び出されます。これは、C#2.0で導入された構文糖衣です。以前のバージョンでは、このようなことをするために独自のIEnumerableIEnumeratorオブジェクトを作成する必要がありました。

このようなコードを理解する最も簡単な方法は、例を入力して、いくつかのブレークポイントを設定し、何が起こるかを確認することです。この例をステップ実行してみてください:

public void Consumer()
{
    foreach(int i in Integers())
    {
        Console.WriteLine(i.ToString());
    }
}

public IEnumerable<int> Integers()
{
    yield return 1;
    yield return 2;
    yield return 4;
    yield return 8;
    yield return 16;
    yield return 16777216;
}

この例をステップ実行すると、Integers()returns への最初の呼び出しが見つかります1。2番目の呼び出しが返さ2れ、行yield return 1は再度実行されません。

これが実際の例です:

public IEnumerable<T> Read<T>(string sql, Func<IDataReader, T> make, params object[] parms)
{
    using (var connection = CreateConnection())
    {
        using (var command = CreateCommand(CommandType.Text, sql, connection, parms))
        {
            command.CommandTimeout = dataBaseSettings.ReadCommandTimeout;
            using (var reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return make(reader);
                }
            }
        }
    }
}

113
この場合は簡単です。ここでは整数を使用して、利回りの戻りがどのように機能するかを示しています。イールドリターンを使用することの良い点は、イテレーターパターンを実装する非常に迅速な方法であるため、物事が遅延評価されることです。
メンデルト2008

111
またyield break;、これ以上アイテムを返却したくない場合にも使用できます。
ロリー

7
yieldはキーワードではありません。もしそうなら、私は収量を識別子として使うことができませんでしたint yield = 500;
Brandin

5
@Brandinは、すべてのプログラミング言語が2種類のキーワードをサポートしているためです。収量は後者のカテゴリに分類されるため、コードはC#コンパイラによって禁止されていません。詳細はこちら:ericlippert.com/2009/05/11/reserved-and-contextual-keywords言語によってキーワードとして認識されない予約語もあることにわくわくします。たとえば、Javaのgotoの場合。ここでは詳細:stackoverflow.com/questions/2545103/...
RBT

7
'If a calling function starts foreach-ing over this object the function is called again until it "yields"'。私には正しく聞こえません。私はいつも「車が歩行者に降伏する」のではなく、「作物が豊かな収穫をもたらす」という文脈でc#yieldキーワードを考えました。
ザック2016年

369

反復。それは、関数の追加の各サイクルでどこにいたかを記憶し、そこからピックアップするステートマシンを「カバーの下」に作成します。


210

Yieldには2つの優れた用途があります。

  1. 一時的なコレクションを作成せずにカスタムの反復を提供するのに役立ちます。

  2. ステートフルな反復を行うのに役立ちます。 ここに画像の説明を入力してください

上記の2つのポイントをより具体的に説明するために、ここで視聴できる簡単なビデオを作成しました


13
ビデオは私を明確に理解するのに役立ちますyield。@ShivprasadKoiralaのコードプロジェクト記事C#Yieldの用途は何ですか?同じ説明の優れた情報源でもある
Dush

また、3つ目のポイントとして、yieldカスタムIEnumeratorを作成する "高速"な方法(クラスにIEnumeratorインターフェイスを実装させる)を追加します。
MrTourkos

私はあなたのビデオShivprasadを見て、yieldキーワードの使用法を明確に説明しました。
Tore Aurstad

ビデオをありがとう!。とてもよく説明されました!
Roblogic

すばらしいビデオですが、不思議に思います。yieldを使用した実装は明らかにクリーンですが、状態を追跡するために(または、状態マシンを作成するために)本質的に独自の一時メモリまたはListを内部で作成する必要があります。では、「Yield」は、実装を単純化して見栄えを良くすること以外に何かしているのでしょうか、それとも他に何かありますか?効率についてはどうですか。Yieldを使用してコードを実行することは、なしよりも効率的/高速ですか?
toughQuestions

135

最近、Raymond Chenは、yieldキーワードに関する興味深い一連の記事も執筆しました。

名目上は反復子パターンを簡単に実装するために使用されますが、状態マシンに一般化することができます。レイモンドを引用しても意味がありません。最後の部分は他の用途にもリンクしています(ただし、Entinのブログの例は特に優れており、非同期セーフコードの記述方法を示しています)。


これは賛成票を投じる必要があります。演算子と内部の目的を彼がどのように説明しているか、甘い。
sajidnizami

3
第1部では、「イールドリターン」の構文糖について説明します。素晴らしい説明!
Dror Weiss

99

一見すると、利回りはIEnumerableを返す.NETシュガーですです。

収量がない場合、コレクションのすべてのアイテムが一度に作成されます。

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        return new List<SomeData> {
            new SomeData(), 
            new SomeData(), 
            new SomeData()
        };
    }
}

収量を使用した同じコードで、項目ごとに返されます。

class SomeData
{
    public SomeData() { }

    static public IEnumerable<SomeData> CreateSomeDatas()
    {
        yield return new SomeData();
        yield return new SomeData();
        yield return new SomeData();
    }
}

収量を使用する利点は、データを消費する関数がコレクションの最初の項目を必要とするだけの場合、残りの項目は作成されないことです。

収量演算子を使用すると、必要に応じてアイテムを作成できます。それはそれを使う正当な理由です。


40

yield return列挙子で使用されます。yieldステートメントが呼び出されるたびに、制御は呼び出し元に返されますが、呼び出し先の状態が確実に維持されます。このため、呼び出し元が次の要素を列挙すると、ステートメントの直後のyieldステートメントから呼び出し先メソッドで実行を継続します。

例でこれを理解してみましょう。この例では、各行に対応して、実行フローの順序について説明しました。

static void Main(string[] args)
{
    foreach (int fib in Fibs(6))//1, 5
    {
        Console.WriteLine(fib + " ");//4, 10
    }            
}

static IEnumerable<int> Fibs(int fibCount)
{
    for (int i = 0, prevFib = 0, currFib = 1; i < fibCount; i++)//2
    {
        yield return prevFib;//3, 9
        int newFib = prevFib + currFib;//6
        prevFib = currFib;//7
        currFib = newFib;//8
    }
}

また、状態は列挙ごとに維持されます。Fibs()メソッドへの別の呼び出しがあるとすると、状態はそのメソッドに対してリセットされます。


2
set prevFib = 1-最初のフィボナッチ数は「1」であり、「0」ではありません
fubo

31

直感的に、キーワードは関数から離れずに関数から値を返します。つまり、コード例では現在のitem値を返し、ループを再開します。より正式には、イテレータのコードを生成するためにコンパイラによって使用されます。イテレータはIEnumerableオブジェクトを返す関数です。MSDNには、いくつか持っている記事それらについてを。


4
正確には、ループを再開するのではなく、親が "iterator.next()"を呼び出すまで一時停止します。
Alex

8
@jitbitそれが私が「直感的に」そして「より正式に」使用した理由です。
Konrad Rudolph

31

リストまたは配列の実装はすべてのアイテムをすぐにロードしますが、yieldの実装は遅延実行ソリューションを提供します。

実際には、アプリケーションのリソース消費を削減するために、必要に応じて最小限の作業を実行することが望ましい場合がよくあります。

たとえば、データベースから何百万ものレコードを処理するアプリケーションがあるとします。遅延実行プルベースモデルでIEnumerableを使用すると、次の利点が得られます。

  • レコード数はアプリケーションのリソース要件に大きな影響を与えないため、スケーラビリティ、信頼性、および予測可能性が向上する可能性があります。
  • コレクション全体が最初にロードされるのを待つのではなく、処理をすぐに開始できるため、パフォーマンスと応答性が向上する可能性があります。
  • アプリケーションが停止、開始、中断、または失敗する可能性があるため、回復可能と使用率が向上する可能性があります。結果の一部のみを実際に使用したすべてのデータをプリフェッチする場合と比較して、進行中のアイテムのみが失われます。
  • 継続的な処理は、一定のワークロードストリームが追加される環境で可能です。

ここでは、リストなどの最初にコレクションを作成する方法と、yieldを使用する方法を比較しています。

リストの例

    public class ContactListStore : IStore<ContactModel>
    {
        public IEnumerable<ContactModel> GetEnumerator()
        {
            var contacts = new List<ContactModel>();
            Console.WriteLine("ContactListStore: Creating contact 1");
            contacts.Add(new ContactModel() { FirstName = "Bob", LastName = "Blue" });
            Console.WriteLine("ContactListStore: Creating contact 2");
            contacts.Add(new ContactModel() { FirstName = "Jim", LastName = "Green" });
            Console.WriteLine("ContactListStore: Creating contact 3");
            contacts.Add(new ContactModel() { FirstName = "Susan", LastName = "Orange" });
            return contacts;
        }
    }

    static void Main(string[] args)
    {
        var store = new ContactListStore();
        var contacts = store.GetEnumerator();

        Console.WriteLine("Ready to iterate through the collection.");
        Console.ReadLine();
    }

コンソール出力
ContactListStore:連絡先1の作成
ContactListStore:連絡先2の
作成ContactListStore:連絡先3の作成
コレクションを反復処理する準備ができました。

注:リスト内の単一のアイテムを要求することなく、コレクション全体がメモリに読み込まれました

収量の例

public class ContactYieldStore : IStore<ContactModel>
{
    public IEnumerable<ContactModel> GetEnumerator()
    {
        Console.WriteLine("ContactYieldStore: Creating contact 1");
        yield return new ContactModel() { FirstName = "Bob", LastName = "Blue" };
        Console.WriteLine("ContactYieldStore: Creating contact 2");
        yield return new ContactModel() { FirstName = "Jim", LastName = "Green" };
        Console.WriteLine("ContactYieldStore: Creating contact 3");
        yield return new ContactModel() { FirstName = "Susan", LastName = "Orange" };
    }
}

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();

    Console.WriteLine("Ready to iterate through the collection.");
    Console.ReadLine();
}

コンソール出力
コレクションを反復処理するための準備完了。

注:コレクションはまったく実行されませんでした。これは、IEnumerableの「据え置き実行」の性質によるものです。アイテムの構築は、本当に必要な場合にのみ行われます。

コレクションを再度呼び出して、コレクションの最初の連絡先を取得するときの動作を観察してみましょう。

static void Main(string[] args)
{
    var store = new ContactYieldStore();
    var contacts = store.GetEnumerator();
    Console.WriteLine("Ready to iterate through the collection");
    Console.WriteLine("Hello {0}", contacts.First().FirstName);
    Console.ReadLine();
}

コンソール出力の
準備
完了コレクションContactYieldStoreの反復 処理:連絡先1の作成
Hello Bob

いいね!クライアントがコレクションからアイテムを「引き出し」たときに最初の連絡先のみが作成されました。


1
この答えにはもっと注意が必要です!Thx
leon22

@ leon22絶対+2
snr

26

コンセプトを理解するための簡単な方法を次に示します。基本的な考え方は、 " foreach"を使用できるコレクションが必要な場合に、コレクションへのアイテムの収集が何らかの理由(データベースからのクエリなど)のためにコストがかかる場合です。また、コレクション全体を必要としないことがよくあるので、一度に1アイテムずつコレクションを作成し、それをコンシューマーに返す関数を作成します(コンシューマーはコレクション作業を早期に終了できます)。

このように考えてみましょう。肉のカウンターに行って、スライスしたハムを1ポンド購入したいとします。肉屋は10ポンドのハムを背負って、それをスライサーマシンに置き、全体をスライスしてから、スライスの山をあなたに返して1ポンドを計ります。(古い方法)。を使用するyieldと、肉屋はスライサーマシンをカウンターに持ってきて、スライスを開始し、1ポンドになるまで各スライスをはかりにスケールし、それをラップして完了です。古い方法は肉屋にとってはより良いかもしれませんが(彼は自分の機械を好きなように整理することができます)、新しい方法はほとんどの場合消費者にとって明らかにより効率的です。


18

yieldキーワードは、あなたが作成することができますIEnumerable<T>上のフォームで反復子ブロック。この反復子ブロックは遅延実行をサポートしており、概念に慣れていない場合は、ほとんど魔法のように見えるかもしれません。ただし、結局のところ、奇妙なトリックなしで実行されるのはコードだけです。

イテレータブロックは、コンパイラが列挙型の列挙がどこまで進んだかを追跡するステートマシンを生成する構文シュガーとして説明できます。enumerableを列挙するには、foreachループを使用することがよくあります。ただし、foreachループも構文上の砂糖です。したがって、実際のコードから2つの抽象概念が削除されているため、最初はすべてがどのように連携しているかを理解するのが難しい場合があります。

非常に単純なイテレーターブロックがあると仮定します。

IEnumerable<int> IteratorBlock()
{
    Console.WriteLine("Begin");
    yield return 1;
    Console.WriteLine("After 1");
    yield return 2;
    Console.WriteLine("After 2");
    yield return 42;
    Console.WriteLine("End");
}

実際のイテレーターブロックには、多くの場合、条件とループがありますが、条件をチェックしてループを展開すると、最終的に次のようになります。 yieldステートメントが他のコードとインターリーブます。

イテレータブロックを列挙するには、foreachループを使用します。

foreach (var i in IteratorBlock())
    Console.WriteLine(i);

ここに出力があります(ここには驚きはありません):

ベギン
1
1の後
2
2の後
42
終わり

上記のようforeachに構文糖です:

IEnumerator<int> enumerator = null;
try
{
    enumerator = IteratorBlock().GetEnumerator();
    while (enumerator.MoveNext())
    {
        var i = enumerator.Current;
        Console.WriteLine(i);
    }
}
finally
{
    enumerator?.Dispose();
}

これを解くために、抽象化を削除したシーケンス図を作成しました:

C#イテレーターブロックシーケンス図

コンパイラーによって生成された状態マシンも列挙子を実装しますが、図をより明確にするために、それらを個別のインスタンスとして示しました。(状態マシンが別のスレッドから列挙される場合、実際には個別のインスタンスを取得しますが、その詳細はここでは重要ではありません。)

イテレータブロックを呼び出すたびに、ステートマシンの新しいインスタンスが作成されます。ただし、イテレーターブロック内のコードはenumerator.MoveNext()、初めて実行されるまで実行されません。これが遅延実行の仕組みです。これは(むしろばかげた)例です:

var evenNumbers = IteratorBlock().Where(i => i%2 == 0);

この時点では、イテレーターは実行されていません。このWhere句は、によって返されたIEnumerable<T>をラップする新しいものを作成しますが、この列挙型はまだ列挙されていません。これは、ループを実行すると発生します。IEnumerable<T>IteratorBlockforeach

foreach (var evenNumber in evenNumbers)
    Console.WriteLine(eventNumber);

enumerableを2回列挙すると、ステートマシンの新しいインスタンスが毎回作成され、イテレーターブロックが同じコードを2回実行します。

以下のようなLINQの方法ということに注意してくださいToList()ToArray()First()Count()などが使用するforeach列挙を列挙するためにループを。たとえばToList()、enumerableのすべての要素を列挙し、それらをリストに格納します。これで、リストにアクセスして、イテレーターブロックを再度実行しなくても、列挙可能なすべての要素を取得できます。のようなメソッドを使用する場合、CPUを使用して列挙可能な要素を複数回生成することと、列挙の要素を格納してそれらに複数回アクセスするためのメモリとの間にはトレードオフがありますToList()


18

これを正しく理解していれば、IEnumerableをyieldで実装する関数の観点からこれをどのように表現するかを次に示します。

  • ここにある。
  • 別のものが必要な場合は、もう一度お電話ください。
  • 私がすでにあなたに与えたものを思い出します。
  • あなたが再び電話したときに、私があなたに別のものを与えることができるかどうかだけがわかります。

シンプルで素晴らしい
ハリー

10

C#のyieldキーワードは、簡単に言うと、イテレーターと呼ばれるコード本体への多くの呼び出しを許可します。イテレーターは、完了する前に戻る方法を知っており、再度呼び出されたときに、中断したところから続行します。つまり、イテレーターを支援します。イテレータが連続した呼び出しで返すシーケンスの各項目ごとに透過的にステートフルになります。

JavaScriptでは、同じ概念がジェネレーターと呼ばれます。


まだ最高の説明。これらはPythonの同じジェネレーターですか?
petrosmm

7

これは、オブジェクトの列挙型を作成する非常にシンプルで簡単な方法です。コンパイラーは、メソッドをラップし、この場合はIEnumerable <object>を実装するクラスを作成します。yieldキーワードがないと、IEnumerable <object>を実装するオブジェクトを作成する必要があります。


5

列挙可能なシーケンスを生成しています。実際に行うのは、ローカルのIEnumerableシーケンスを作成し、それをメソッドの結果として返すことです。


3

このリンクには簡単な例があります

さらに簡単な例がここにあります

public static IEnumerable<int> testYieldb()
{
    for(int i=0;i<3;i++) yield return 4;
}

yield returnはメソッドから返らないことに注意してください。あなたもWriteLine後に置くことができますyield return

上記は、4つの整数のIEnumerableを生成します4,4,4,4

ここでWriteLine。リストに4を追加し、abcを出力してから、リストに4を追加して、メソッドを完了し、実際にメソッドから戻ります(メソッドが完了したら、戻り値のないプロシージャで発生します)。しかし、これには値、sのIEnumerableリストintが含まれ、完了時に返されます。

public static IEnumerable<int> testYieldb()
{
    yield return 4;
    console.WriteLine("abc");
    yield return 4;
}

また、yieldを使用する場合、返されるのは関数と同じ型ではないことにも注意してください。IEnumerableリスト内の要素のタイプです。

メソッドの戻り値の型としてを使用しIEnumerableます。メソッドの戻り値の型がintまたはList<int>あり、を使用している場合はyield、コンパイルされません。あなたはIEnumerableyieldなしでメソッドの戻り値の型を使用できますが、おそらくそれなしではyieldを使用できないようですIEnumerable

そして、それを実行するには、特別な方法で呼び出す必要があります。

static void Main(string[] args)
{
    testA();
    Console.Write("try again. the above won't execute any of the function!\n");

    foreach (var x in testA()) { }


    Console.ReadLine();
}



// static List<int> testA()
static IEnumerable<int> testA()
{
    Console.WriteLine("asdfa");
    yield return 1;
    Console.WriteLine("asdf");
}

ノート・SelectManyを理解しようとするならば、それは歩留まりもジェネリック医薬品を使用しています...この例では、役立つかもしれないpublic static IEnumerable<TResult> testYieldc<TResult>(TResult t) { yield return t; }public static IEnumerable<TResult> testYieldc<TResult>(TResult t) { return new List<TResult>(); }
barlop

とても良い説明のように見えます!これは受け入れられた答えかもしれません。
pongapundit

@pongapunditのおかげで、私の答えは確かに明確で単純ですが、私自身はあまり成果を上げていません。他の回答者は、私よりもはるかに多くの経験とその使用法の知識を持っています。ここで私がイールドについて書いたのは、おそらく、こことそのdotnetperlsリンクでいくつかの答えを理解しようとして頭を掻いたことです!しかし、(私が言っyield returnた単純なものを除いて)それをよく知らず、それをあまり使用しておらず、その使用についてもあまり知らないので、これは受け入れられるべきものではないと思います。
barlop

3

Yieldキーワードの重要なポイントの1つは、レイジー実行です。レイジー実行とは、必要なときに実行することです。それを置くより良い方法は、例を挙げることです

例:Yieldを使用しない、つまり遅延実行しない。

        public static IEnumerable<int> CreateCollectionWithList()
        {
            var list =  new List<int>();
            list.Add(10);
            list.Add(0);
            list.Add(1);
            list.Add(2);
            list.Add(20);

            return list;
        }

例:Yield ie Lazy Executionの使用。

    public static IEnumerable<int> CreateCollectionWithYield()
    {
        yield return 10;
        for (int i = 0; i < 3; i++) 
        {
            yield return i;
        }

        yield return 20;
    }

次に、両方のメソッドを呼び出します。

var listItems = CreateCollectionWithList();
var yieldedItems = CreateCollectionWithYield();

listItemsの中に5つのアイテムがあることに気づくでしょう(デバッグ中にlistItemsの上にマウスを置いてください)。一方、yieldItemsには、項目ではなくメソッドへの参照のみが含まれます。つまり、メソッド内のアイテムを取得するプロセスを実行していません。必要なときにのみデータを取得する非常に効率的な方法。収量の実際の実装は、Entity FrameworkやNHibernateなどのORMで確認できます。


-3

Rubyの良さを取り入れようとしている:)
概念:これは、配列の各要素を出力するRubyコードのサンプルです。

 rubyArray = [1,2,3,4,5,6,7,8,9,10]
    rubyArray.each{|x| 
        puts x   # do whatever with x
    }

Arrayのeachメソッドの実装は、それぞれの呼び出し元(「puts x」)を制御します。アレイの要素がきちんとXとして提示します。呼び出し元は、xを使用して必要なことをすべて実行できます。

ただし、.Netはここまでは行きません。メンデルトの応答に見られるように、C#は利回りをIEnumerableと組み合わせて、呼び出し側でforeachループを作成するように強制しているようです。少しエレガントではありません。

//calling code
foreach(int i in obCustomClass.Each())
{
    Console.WriteLine(i.ToString());
}

// CustomClass implementation
private int[] data = {1,2,3,4,5,6,7,8,9,10};
public IEnumerable<int> Each()
{
   for(int iLooper=0; iLooper<data.Length; ++iLooper)
        yield return data[iLooper]; 
}

7
-1この答えは私には正しく聞こえません。はい、C#yieldはと連動してIEnumerableおり、C#には「ブロック」というRubyの概念がありません。しかし、C#にはラムダがあり、ForEachRubyとよく似た方法でメソッドを実装できますeach。ただし、そうすることをお勧めします。
rsenna 2013年

さらに良い:public IEnumerable <int> Each(){int index = 0; 戻りデータを返す[インデックス++]; }
ata
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.