7

実際の状況で「yield」キーワードを使用する場所をまだ見つけようとしています。

私はこの件に関するこのスレッドを見ます

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

しかし、受け入れられた回答では、誰かが Integers() を反復している例としてこれを持っています

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

しかし、なぜ使用しないのですか

list<int>

代わりにここに。より簡単なようです..

4

10 に答える 10

22

List を作成して返す場合 (100 万個の要素があるとします)、それはメモリの大きな塊であり、それを作成する作業でもあります。

呼び出し元は、最初の要素が何であるかだけを知りたい場合があります。または、リスト全体をメモリ内に構築してからファイルに書き込むのではなく、取得したとおりにファイルに書き込みたい場合があります。

そのため、yield return を使用する方が理にかなっています。リスト全体を作成して返すのとそれほど違いはないように見えますが、呼び出し元がリストの最初の項目を見る前にリスト全体をメモリ内に作成する必要がないため、大きく異なります。

発信者が次のように言うとき:

foreach (int i in Integers())
{
   // do something with i
}

ループが新しい i を必要とするたびに、Integers() 内のコードをもう少し実行します。その関数のコードは、yield returnステートメントにヒットすると「一時停止」します。

于 2008-12-21T12:34:41.933 に答える
9

Yield を使用すると、返す前にすべてを収集する必要なく、データを生成するメソッドを構築できます。途中で複数の値を返すと考えてください。

ポイントを説明するいくつかの方法を次に示します

public IEnumerable<String> LinesFromFile(String fileName)
{
    using (StreamReader reader = new StreamReader(fileName))
    {
        String line;
        while ((line = reader.ReadLine()) != null)
            yield return line;
    }
}

public IEnumerable<String> LinesWithEmails(IEnumerable<String> lines)
{
    foreach (String line in lines)
    {
        if (line.Contains("@"))
            yield return line;
    }
}

これら 2 つの方法のどちらも、ファイルの内容全体をメモリに読み込むことはありませんが、次のように使用できます。

foreach (String lineWithEmail in LinesWithEmails(LinesFromFile("test.txt")))
    Console.Out.WriteLine(lineWithEmail);
于 2008-12-21T12:38:46.827 に答える
4

を使用yieldして、任意のイテレータを構築できます。これは、一連の遅延評価 (たとえば、一度にすべてを読み取らずにファイルまたはデータベースから行を読み取るため、メモリに保持するには多すぎる可能性があります) である可能性や、List<T>.

C# in Depthには、反復子ブロックに関する無料の章 (6)があります。

私は最近、スマート ブルート フォース アルゴリズムの使用についてブログを書きました。yield

遅延ファイル リーダーの例:

    static IEnumerable<string> ReadLines(string path) {
        using (StreamReader reader = File.OpenText(path)) {
            string line;
            while ((line = reader.ReadLine()) != null) {
                yield return line;
            }
        }
    }

これは完全に「怠け者」です。列挙を開始するまでは何も読み取られず、1 行だけがメモリに保持されます。

LINQ-to-Objects では反復子ブロック ( ) が広範囲にyield使用されることに注意してください。たとえば、Where拡張子は基本的に次のとおりです。

   static IEnumerable<T> Where<T>(this IEnumerable<T> data, Func<T, bool> predicate) {
        foreach (T item in data) {
            if (predicate(item)) yield return item;
        }
    }

繰り返しますが、完全に怠惰です。すべてを強制的にメモリにロードすることなく、複数の操作を連鎖させることができます。

于 2008-12-21T12:35:46.270 に答える
2

リストベースのアプローチとは異なり、コレクション全体が一度にメモリにロードされることはないため、yield を使用すると、潜在的に無限のサイズのコレクションを処理できます。たとえば、すべての素数の IEnumerable<> は、素数を見つけるための適切なアルゴリズムによってバックオフできますが、リスト アプローチは常にサイズが有限であるため不完全です。この例では、yield を使用すると、次の要素の処理を必要になるまで延期することもできます。

于 2008-12-21T12:38:58.137 に答える
1

私にとっての実際の状況は、よりスムーズに入力するのに時間がかかるコレクションを処理したいときです。

行に沿って何かを想像してください(疑似コード):

public IEnumberable<VerboseUserInfo> GetAllUsers()
{
    foreach(UserId in userLookupList)
    {
        VerboseUserInfo info = new VerboseUserInfo();

        info.Load(ActiveDirectory.GetLotsOfUserData(UserId));
        info.Load(WebSerice.GetSomeMoreInfo(UserId));

        yield return info;
    }
}

コレクション内のアイテムの処理を開始する前に、コレクションが作成されるまで 1 分待つ必要はありません。すぐに開始して、発生したときにユーザー インターフェイスに報告することができます。

于 2008-12-21T12:43:06.740 に答える
1

リストを返す代わりに常にyieldを使用したいわけではありません。あなたの例では、実際に整数のリストを返すためにyieldを使用しています。可変リストまたは不変シーケンスが必要かどうかに応じて、リストまたはイテレータ (またはその他の可変/不変コレクション) を使用できます。

しかし、利回りを使用する利点があります。

  • Yield は、遅延評価された反復子を構築する簡単な方法を提供します。(MoveNext() メソッドが呼び出されたときに次の要素を順番に取得するためのコードのみが実行されることを意味し、メソッドが再度呼び出されるまで、反復子はそれ以上計算を行わずに戻ります)

  • Yield は内部でステート マシンを構築します。これにより、ジェネリック ジェネレーターの状態をコーディングする必要がなくなるため、割り当てられた作業を節約できます => より簡潔でシンプルなコードになります。

  • Yield は、最適化されたスレッド セーフなイテレータを自動的に構築するため、それらの構築方法に関する詳細を省くことができます。

  • Yield は一見したよりもはるかに強力であり、単純な反復子を作成するだけでなく、さまざまな用途に使用できます。このビデオをチェックして、Jeffrey Richter と彼の AsyncEnumeratorを確認し、yield を使用して非同期パターンを使用したコーディングを簡単にする方法を確認してください。

于 2008-12-21T13:15:56.440 に答える
0

リストを手動でディープコピーする必要がある.netの欠点を克服するために、これを思いつきました。

私はこれを使用します:

static public IEnumerable<SpotPlacement> CloneList(List<SpotPlacement> spotPlacements)
{
    foreach (SpotPlacement sp in spotPlacements)
    {
        yield return (SpotPlacement)sp.Clone();
    }
}

そして別の場所で:

public object Clone()
{
    OrderItem newOrderItem = new OrderItem();
    ...
    newOrderItem._exactPlacements.AddRange(SpotPlacement.CloneList(_exactPlacements));
    ...
    return newOrderItem;
}

これを行うワンライナーを考え出そうとしましたが、匿名メソッドブロック内でyieldが機能しないため、不可能です。

編集:

さらに良いことに、一般的な List cloner を使用します。

class Utility<T> where T : ICloneable
{
    static public IEnumerable<T> CloneList(List<T> tl)
    {
        foreach (T t in tl)
        {
            yield return (T)t.Clone();
        }
    }
}
于 2009-09-30T09:50:40.393 に答える
0

さまざまなコレクションを反復したい場合があります。

public IEnumerable<ICustomer> Customers()
{
        foreach( ICustomer customer in m_maleCustomers )
        {
            yield return customer;
        }

        foreach( ICustomer customer in m_femaleCustomers )
        {
            yield return customer;
        }

        // or add some constraints...
        foreach( ICustomer customer in m_customers )
        {
            if( customer.Age < 16 )
            {
                yield return customer;
            }
        }

        // Or....            
        if( Date.Today == 1 )
        {
            yield return m_superCustomer;
        }

}
于 2008-12-21T12:33:55.290 に答える
0

遅延評価とメモリ使用量についてここで誰もが言ったことすべてに同意し、yieldキーワードを使用するイテレータが有用であることがわかった別のシナリオを追加したいと思いました。イテレータを使用すると非常に便利なデータに対して、コストがかかる可能性のある一連の処理を実行する必要がある場合に遭遇しました。ファイル全体をすぐに処理したり、独自の処理パイプラインを展開したりするのではなく、単純に次のような反復子を使用できます。

IEnumerable<double> GetListFromFile(int idxItem)
{
    // read data from file
    return dataReadFromFile;
}

IEnumerable<double> ConvertUnits(IEnumerable<double> items)
{
    foreach(double item in items)
        yield return convertUnits(item);
}

IEnumerable<double> DoExpensiveProcessing(IEnumerable<double> items)
{
    foreach(double item in items)
        yield return expensiveProcessing(item);
}

IEnumerable<double> GetNextList()
{
    return DoExpensiveProcessing(ConvertUnits(GetListFromFile(curIdx++)));
}

ここでの利点は、入力と出力をすべての関数に保持することでIEnumerable<double>、私の処理パイプラインが完全に構成可能で、読みやすく、遅延評価されるため、本当に必要な処理だけを実行すればよいことです。これにより、応答性に影響を与えることなく、ほぼすべての処理を GUI スレッドに入れることができるため、スレッドの問題について心配する必要がありません。

于 2008-12-21T13:04:04.933 に答える