通常のIEnumerableを使用できるのに、なぜyieldキーワードを使用するのですか?


171

このコードを考えると:

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

なぜ私はそれをこのようにコード化すべきではないのですか?:

IEnumerable<object> FilteredList()
{
    var list = new List<object>(); 
    foreach( object item in FullList )
    {
        if( IsItemInPartialList( item ) )
            list.Add(item);
    }
    return list;
}

yieldキーワードが何をするのか私は理解しています。コンパイラーに特定の種類のもの(イテレーター)を作成するように指示します。しかし、なぜそれを使うのですか?コードが少し少ないだけでなく、私にとって何ができるのでしょうか?


28
これは単なる例であることを理解していますが、実際には、コードは次のように記述します。FullList.Where(IsItemInPartialList):)
BlueRaja-Danny Pflughoeft

回答:


241

を使用yieldすると、コレクションが遅延します。

最初の5つのアイテムだけが必要だとします。あなたのやり方で、最初の5つのアイテムを取得するには、リスト全体をループする必要があります。ではyield、最初の5つの項目のみをループします。


14
使用FullList.Where(IsItemInPartialList)は同様に怠惰になることに注意してください。ただ、コンパイラが生成するカスタム--- gunk ---コードがはるかに少なくて済みます。また、開発者が書いたり保守したりする時間を短縮できます。(もちろん、それはこの例にすぎません)
sehe

4
それがLinqですね。Linqが内部で非常に似たようなことを行うと思います。
Robert Harvey

1
はい、Linqはyield return可能な限り遅延実行()を使用しました。
Chad Schouggins 2013年

11
yield returnステートメントが実行されない場合でも、空のコレクション結果が返されるので、null参照例外を心配する必要がないことを忘れないでください。チョコレートの振りかけると、利回りは素晴らしいです。
Razor

127

イテレータブロックの利点は、遅延して動作することです。したがって、次のようなフィルタリングメソッドを記述できます。

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

これにより、一度に複数のアイテムをバッファリングすることなく、好きなだけストリームをフィルタリングできます。たとえば、返されたシーケンスの最初の値のみが必要な場合、なぜすべてを新しいリストにコピーするのでしょうか?

別の例として、イテレーターブロックを使用して無限ストリームを簡単に作成できます。たとえば、乱数のシーケンスは次のとおりです。

public static IEnumerable<int> RandomSequence(int minInclusive, int maxExclusive)
{
    Random rng = new Random();
    while (true)
    {
        yield return rng.Next(minInclusive, maxExclusive);
    }
}

リストに無限シーケンスをどのように保存しますか?

私のEdulinqブログシリーズでは、反復子ブロックを多用するLINQ to Objectsのサンプル実装を紹介しています。LINQは基本的にそれが可能な場所では面倒です-そして、物事をリストに入れることは単にそのように機能しません。


1
あなたが好きかどうかわかりませRandomSequenceん。私にとって、IEnumerableは、何よりもまず、foreachで反復できることを意味しますが、これは明らかにここで無限ループにつながるでしょう。これはIEnumerableコンセプトのかなり危険な誤用だと思いますが、YMMVです。
Sebastian Negraszus 2013年

5
@SebastianNegraszus:一連の乱数は論理的に無限です。IEnumerable<BigInteger>たとえば、フィボナッチ数列を簡単に作成できます。あなたはforeachそれを使うことができますが、それIEnumerable<T>が有限であるという保証については何もありません。
Jon Skeet 2013年

42

「リスト」コードを使用すると、次のステップに渡す前に、リスト全体を処理する必要があります。"yield"バージョンは、処理されたアイテムをすぐに次のステップに渡します。その「次のステップ」に「.Take(10)」が含まれている場合、「yield」バージョンは最初の10アイテムのみを処理し、残りのアイテムは忘れます。「リスト」コードはすべてを処理します。

これは、多くの処理を行う必要がある場合や、処理するアイテムのリストが長い場合に、最も大きな違いが見られることを意味します。


23

yieldリストにない項目を返すために使用できます。キャンセルされるまでリストを無限に反復できる小さなサンプルを次に示します。

public IEnumerable<int> GetNextNumber()
{
    while (true)
    {
        for (int i = 0; i < 10; i++)
        {
            yield return i;
        }
    }
}

public bool Canceled { get; set; }

public void StartCounting()
{
    foreach (var number in GetNextNumber())
    {
        if (this.Canceled) break;
        Console.WriteLine(number);
    }
}

これは書いています

0
1
2
3
4
5
6
7
8
9
0
1
2
3
4

...等。キャンセルされるまでコンソールに。


10
object jamesItem = null;
foreach(var item in FilteredList())
{
   if (item.Name == "James")
   {
       jamesItem = item;
       break;
   }
}
return jamesItem;

上記のコードを使用してFilteredList()をループし、item.Name == "James"がリストの2番目の項目で満たされると仮定すると、使用yieldするメソッドは2回生成されます。これは遅延動作です。

一方、listを使用するメソッドは、n個のオブジェクトすべてをリストに追加し、完全なリストを呼び出し元のメソッドに渡します。

これは、IEnumerableとIListの違いを強調できるユースケースです。


7

私がの使用について見た中で最も良い実世界の例はyield、フィボナッチ数列を計算することです。

次のコードを検討してください。

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine(string.Join(", ", Fibonacci().Take(10)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(15).Take(1)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(10).Take(5)));
        Console.WriteLine(string.Join(", ", Fibonacci().Skip(100).Take(1)));
        Console.ReadKey();
    }

    private static IEnumerable<long> Fibonacci()
    {
        long a = 0;
        long b = 1;

        while (true)
        {
            long temp = a;
            a = b;

            yield return a;

            b = temp + b;
        }
    }
}

これは戻ります:

1, 1, 2, 3, 5, 8, 13, 21, 34, 55
987
89, 144, 233, 377, 610
1298777728820984005

無限系列をすばやく簡単に計算でき、Linq拡張機能を使用して必要なものだけをクエリできるため、これは素晴らしいことです。


5
フィボナッチ数列計算で「実世界」を見ることができません。
2013年

これは本当に「現実の世界」ではないことに同意しますが、これは素晴らしいアイデアです。
ケーシー

1

[yield]を使用する理由 コードが少し少ないだけでなく、私にとって何ができるでしょうか?

時にはそれは便利なこともあれば、そうでないこともあります。データのセット全体を調べて返す必要がある場合は、オーバーヘッドが発生するだけだったため、yieldを使用しても何のメリットもありません。

収量が本当に優れているのは、部分的なセットのみが返されるときです。最良の例はソートです。今年の日付と金額を含むオブジェクトのリストがあり、その年の最初の数(5)件のレコードを表示するとします。

これを行うには、リストを日付の昇順でソートし、最初の5つを取得する必要があります。これが収量なしで行われた場合、最後の2つの日付が正しいことを確認するまで、リスト全体をソートする必要があります。

ただし、収量の場合、最初の5つのアイテムが確立されると、並べ替えが停止し、結果が利用可能になります。これにより、時間を大幅に節約できます。


0

yield returnステートメントでは、一度に1つのアイテムのみを返すことができます。リスト内のすべてのアイテムを収集し、再びそのリストを返します。これはメモリのオーバーヘッドです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.