953

IList<> のフラグメントのみを公開する方法の質問では、回答の 1 つに次のコード スニペットがありました。

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

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

4

19 に答える 19

864

ここyieldでは、コンテキスト キーワードが実際に多くのことを行います。

この関数は、インターフェイスを実装するオブジェクトを返しIEnumerable<object>ます。呼び出し元の関数がforeachこのオブジェクトに対して開始した場合、その関数は "yield" するまで再度呼び出されます。これは、 C# 2.0で導入されたシンタックス シュガーです。以前のバージョンでは、このようなことを行うには独自のオブジェクトを作成する必要がIEnumerableありIEnumeratorました。

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

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;
}

例をステップ実行すると、 return の最初の呼び出しが見つかりIntegers()ます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);
                }
            }
        }
    }
}
于 2008-09-02T13:23:16.200 に答える
408

反復。関数の追加サイクルごとにどこにいたかを記憶し、そこから取得する「カバーの下」のステートマシンを作成します。

于 2008-09-02T13:17:23.250 に答える
149

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

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

于 2008-09-02T13:27:46.767 に答える
128

一見すると、yield return はIEnumerableを返す.NETシュガーです。

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

class SomeData
{
    public SomeData() { }

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

yield を使用した同じコードでは、アイテムごとに返されます。

class SomeData
{
    public SomeData() { }

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

yield を使用する利点は、データを使用する関数が単にコレクションの最初のアイテムを必要とする場合、残りのアイテムは作成されないことです。

yield オペレーターを使用すると、必要に応じてアイテムを作成できます。それがそれを使用する正当な理由です。

于 2015-01-17T14:23:08.620 に答える
53

リストまたは配列の実装はすべてのアイテムをすぐにロードしますが、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
の作成 コレクションを反復処理する準備ができました。

注: リスト内の 1 つの項目を要求することさえせずに、コレクション全体がメモリに読み込まれました。

収量例

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

良い!クライアントがアイテムをコレクションから「引き抜いた」とき、最初の連絡先のみが構築されました。

于 2015-12-04T19:52:36.617 に答える
33

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

于 2008-09-02T13:19:09.533 に答える
18

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

例: Yield を使用しない、つまり Lazy Execution を使用しない。

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、つまり 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 はアイテムではなくメソッドへの参照のみを持ちます。つまり、メソッド内でアイテムを取得するプロセスが実行されていません。必要なときにだけデータを取得する非常に効率的な方法。yield の実際の実装は、Entity Framework や NHibernate などの ORM で見ることができます。

于 2019-10-17T23:35:12.753 に答える
13

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

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

于 2013-01-14T22:49:09.723 に答える
10

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

于 2008-09-02T13:17:36.340 に答える
7

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

于 2008-09-02T13:18:25.493 に答える
4

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

さらに簡単な例はこちら

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

メソッドから yield return が返されないことに注意してください。WriteLineの後に a を付けることもできますyield return

上記は、4 ints 4,4,4,4 の IEnumerable を生成します

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

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

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

メソッドの戻り値の型として yield を使用しIEnumerableます。メソッドの戻り値の型がintorList<int>で、 を使用するyieldと、コンパイルされません。yield なしでメソッドの戻り値の型を使用できますが、IEnumerableメソッドの戻り値の型がないと 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");
}
于 2016-05-01T01:25:08.987 に答える
-5

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

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

配列の各メソッドの実装は、配列の要素がxとして適切に表示されるように、呼び出し元(' puts x')を制御します。その後、呼び出し元はxで必要なことをすべて実行できます。

ただし、 .Netはここでは完全には機能しません。C#はyieldと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]; 
}
于 2008-09-02T14:06:52.493 に答える