22

歩留まりは今まで理解するのが難しいものです。しかし今、私はそれを手に入れています。これで、プロジェクトでListを返すと、Microsoftのコード分析で警告が表示されます。したがって、通常、必要なすべての論理部分を実行し、リストをIEnumerableとして返します。両者の違いを知りたい。イールドリターンを行っているかどうかを意味します。

これは私が示している非常に単純な例です。通常、コードは少し複雑です。

private static IEnumerable<int> getIntFromList(List<int> inputList)
{
    var outputlist = new List<int>();
    foreach (var i in inputList)
    {
        if (i %2 ==0)
        {
            outputlist.Add(i);
        }
    }

    return outputlist.AsEnumerable();
}

private static IEnumerable<int> getIntFromYeild(List<int> inputList)
{
    foreach (var i in inputList)
    {
        if (i%2 == 0)
        {
            yield return i;
        }
    }
}

私が見ることができる重要な利点の1つは、行が少ないことです。しかし、他に何かメリットはありますか?Listの代わりにyieldを使用するために、IEnumearbleを返す関数を変更および更新する必要がありますか?物事を行うための最良の方法またはより良い方法は何ですか?

ここでは、Listに対して単純なラムダ式を使用できますが、通常はそうではありません。この例は、コーディングの最良のアプローチを理解するためのものです。

4

5 に答える 5

47

最初の例は、まだすべての作業を熱心に行い、メモリ内にリストを作成しています。実際、への呼び出しAsEnumerable()は無意味です-あなたは次のように使用したほうがよいでしょう:

return outputlist;

2番目の例は怠惰です-クライアントがそこからデータをプルするのと同じくらい必要なだけの作業を行います。

違いを示す最も簡単な方法は、おそらくステートメントConsole.WriteLine内に呼び出しを入れることです。if (i % 2 == 0)

Console.WriteLine("Got a value to return: " + i);

次に、クライアントコードに呼び出しを入れた場合、たとえばConsole.WriteLine

foreach (int value in getIntFromList(list))
{
    Console.WriteLine("Received value: " + value);
}

...最初のコードでは、最初にすべての「Got a value」行が表示され、次にすべての「Receivedvalue」行が表示されます。イテレータブロックを使用すると、それらがインターリーブされていることがわかります。

ここで、コードが実際に何か高価なことをしていて、リストが非常に長く、クライアントが最初の3つの値のみを必要としていると想像してください...最初のコードでは、無関係な作業を大量に行っていることになります。怠惰なアプローチでは、「ジャストインタイム」の方法で、必要なだけの作業を行います。2番目のアプローチでも、すべての結果をメモリにバッファリングする必要はありません。ここでも、入力リストが非常に大きい場合は、で単一の値のみを使用したい場合でも、出力リストも大きくなります。時間。

于 2013-01-21T07:26:57.367 に答える
17

重要な点yield returnは、バッファリングされていないことです。イテレータブロックはステートマシンであり、データが繰り返されると再開します。これにより、メモリ内の巨大なリストを回避できるため、非常に大きなデータソース(または無限のリスト)に便利です。

以下は、完全に明確に定義されたイテレータブロックであり、正常に反復できます。

Random rand = new Random();
while(true) yield return rand.Next();

そして私達は次のようなことをすることができます:

for(int i in TheAbove().Take(20))
    Console.WriteLine(i);

明らかに、最後まで繰り返すもの(Count()など)は、終了せずに永久に実行されます-素晴らしいアイデアではありません。

あなたの例では、コードはおそらく過度に複雑です。バージョンは次のList<int>ようになります。

return new List<int>(inputList);

ちょっとあなたyield returnが何をしたいかに依存します:最も単純に、それはただである可能性があります:

foreach(var item in inputList) yield return item;

明らかにそれはまだソースデータを見ているでしょうが:への変更inputListはイテレータを壊す可能性があります。「それでいい」と思うなら、率直に言って、次のようにしたほうがいいでしょう。

return inputList;

それがうまくいかない場合、この場合、イテレータブロックは少しやり過ぎであり、次のようになります。

return new List<int>(inputList);

十分なはずです。

完全を期すために:AsEnumerable元のソースを返すだけで、castと入力します。それは:

return inputList;

バージョン。これが懸念される場合は、リストを保護しないという点で、これには重要な考慮事項があります。だからあなたが考えているなら:

return someList.AsEnumerable(); // so they can only iterate it, not Add

その後、それは機能しません。邪悪な発信者はまだ次のことができます。

var list = (IList<int>) theAbove;
int mwahaahahaha = 42;
list.Add(mwahaahahaha);
于 2013-01-21T07:29:18.403 に答える
1

大きな違い:2番目(歩留まり)では、メモリの無駄が少なくなります。1つ目は、基本的にリストのコピーをメモリに作成します。

大きな違い:呼び出し元がサンプル2の元のリストを操作すると、壊れますが、サンプル1では壊れません(コピーを繰り返すため)。

したがって、2つのコードは同一ではありません。エッジケースについて考えず、ストレートケースのみを見て、すべての副作用を無視する場合にのみ同じです。

結果として、ところで、例2は、2番目のリストを割り当てないため、より高速です。

于 2013-01-21T07:27:30.150 に答える
0

違いは実行時間にあります。

最初の例では、関数のコードは関数が終了する前に実行されます。リスト全体が作成され、IEnumerableとして返されます。

2番目の例では、関数の終了時に関数のコードは実際には実行されません。代わりに、関数が終了するとIEnumerableが返され、後でそのIEnumerableを反復処理すると、コードが実行されます。

特に、2番目の例でIEnumerableの最初の3つの要素のみを反復処理する場合、forループは3つの要素を取得するのに十分な回数だけ反復し、それ以上は反復しません。

于 2013-01-21T07:27:36.347 に答える
0

イールドを使用すると、コンパイラは、事前に生成されたリストよりも高速に動作するイテレータパターンのコードを生成します。これは次のようなものです。

namespace Yield
{
    class UserCollection
    {
        public static IEnumerable Power()
        {
            return new ClassPower(-2);
        }

        private sealed class ClassPower : IEnumerable<object>, IEnumerable, IEnumerator<object>, IEnumerator, IDisposable
        {

            private int state;
            private object current;
            private int initialThreadId;

        public ClassPower(int state)
        {
            this.state = state;
            this.initialThreadId = Thread.CurrentThread.ManagedThreadId;
        }

        bool IEnumerator.MoveNext()
        {
            switch (this.state)
            {
                case 0:
                    this.state = -1;
                    this.current = "Hello world!";
                    this.state = 1;
                    return true;

                case 1:
                    this.state = -1;
                    break;
            }
            return false;
        }

        IEnumerator<object> IEnumerable<object>.GetEnumerator()
        {
            if ((Thread.CurrentThread.ManagedThreadId == this.initialThreadId) && (this.state == -2))
            {
                this.state = 0;
                return this;
            }
            return new UserCollection.ClassPower(0);
        }

        IEnumerator IEnumerable.GetEnumerator()
        {       
            return (this as IEnumerable<object>).GetEnumerator();
        }

        void IEnumerator.Reset()
        {
            throw new NotSupportedException();
        }

        void IDisposable.Dispose()
        {
        }

        object IEnumerator<object>.Current
        {
            get
            {
                return this.current;
            }
        }

        object IEnumerator.Current
        {
            get
            {
                return this.current;
            }
        }
    }
}

}

于 2013-01-21T07:33:00.940 に答える