デバッガーで最近実行したスニペットについて説明が欲しいのですが、実際には理解できません。
C#コースをPluralSightで受講しており、現在のトピックはyield
にあり、キーワードとともにIEnumerable<T>
を返しています。
IEnumerable
のVendors
コレクション(Id
、CompanyName
およびEmail
を含む単純なクラス)を返す、この非常に基本的な関数を持っています。 ):
public IEnumerable<Vendor> RetrieveWithIterator()
{
this.Retrieve(); // <-- I've got a breakpoint here
foreach(var vendor in _vendors)
{
Debug.WriteLine($"Vendor Id: {vendor.VendorId}");
yield return vendor;
}
}
そして、私は関数をテストするために使用しているユニットテストでこのコードを持っています:
var vendorIterator = repository.RetrieveWithIterator(); // <-- Why don't it enter function?
foreach (var item in vendorIterator) // <-- But starts here?
{
Debug.WriteLine(item);
}
var actual = vendorIterator.ToList();
私が本当に理解できていないようで、多くの初心者が同じ問題を抱えていると確信しているのは、RetrieveWithIterator
への最初の呼び出しが関数を開始しないのはなぜですか。返されたIEnumerable
コレクションの反復を開始します(コメントを参照)。
これは遅延実行と呼ばれ、yield
はレイジーであり、必要なだけ機能します。
これには非常に多くの利点があり、その1つは無限に見える列挙を作成できることです。
public IEnumerable<int> InfiniteOnes()
{
while (true)
yield 1;
}
次のことを想像してみてください。
var infiniteOnes = InfiniteOnes();
熱心に実行されますが、StackOverflow
例外が非常に楽しく発生します。
一方、その面倒なので、次のことができます。
var infiniteOnes = InfiniteOnes();
//.... some code
foreach (var one in infiniteOnes.Take(100)) { ... }
以降、
foreach (var one in infiniteOnes.Take(10000)) { ... }
イテレーターブロックは、必要なときにのみ実行されます。列挙が反復されるとき、前ではなく後。
Msdnから:
遅延実行とは、実際の値が実際に必要になるまで式の評価が遅延することを意味します。特に一連のチェーンされたクエリまたは操作を含むプログラムで、大きなデータコレクションを操作する必要がある場合、遅延実行によりパフォーマンスが大幅に向上します。最良の場合、遅延実行では、ソースコレクション全体で1回の反復のみが可能です。
遅延実行は、イテレーターブロック内で使用される場合、Cymanage言語のyieldキーワード(yield-returnステートメントの形式)によって直接サポートされます。そのようなイテレータは、IEnumerator
またはIEnumerator<T>
タイプ(または派生タイプ)のコレクションを返す必要があります。
var vendorIterator = repository.RetrieveWithIterator(); // <-- Lets deferred the execution
foreach (var item in vendorIterator) // <-- execute it because we need it
{
Debug.WriteLine(item);
}
var actual = vendorIterator.ToList();
遅延実行を実装するメソッドを作成するときは、遅延評価と熱心な評価のどちらを使用してメソッドを実装するかを決定する必要もあります。
遅延評価は通常、オーバーヘッド評価処理をコレクションの評価全体に均等に分散し、一時データの使用を最小限に抑えるため、パフォーマンスが向上します。もちろん、一部の操作では、中間結果を具体化する以外に選択肢がありません。
必要なときにそれらをループするとアイテムを取得します。この方法では、最初の4つの結果のみが必要であり、その後ブレークすると、それ以上何も生成されず、処理能力が節約されます。
From MS Docs :
Yield returnステートメントを使用して、各要素を一度に1つずつ返します。 foreachステートメントまたはLINQクエリを使用して、イテレーターメソッドを使用します。 foreachループの各反復は、反復子メソッドを呼び出します。イテレータメソッドでyield returnステートメントに到達すると、式が返され、コード内の現在の場所が保持されます。次に反復関数が呼び出されたときに、その場所から実行が再開されます。反復を終了するには、yield breakステートメントを使用できます。
注-生成するメソッドの結果に対して.ToList()
を実行すると、単一のリストを返したかのように機能するため、yieldの目的が無効になります。