yield return x;C#メソッド内での使用について私が見たすべての例は、リスト全体を返すだけで同じ方法で実行できます。そのような場合、yield return構文を使用することとリストを返すことの利点または利点はありますか?
また、yield return完全なリストを返すことができなかったシナリオの種類は何ですか?
yield return x;C#メソッド内での使用について私が見たすべての例は、リスト全体を返すだけで同じ方法で実行できます。そのような場合、yield return構文を使用することとリストを返すことの利点または利点はありますか?
また、yield return完全なリストを返すことができなかったシナリオの種類は何ですか?
yieldsを持つことの最大の利点は、さらに別の中間変数に名前を付ける必要がないことです。
回答:
しかし、自分でコレクションを作成している場合はどうでしょうか。
一般に、イテレータを使用して、オブジェクトのシーケンスを遅延生成できます。たとえば、Enumerable.Rangeメソッドには内部的にいかなる種類のコレクションもありません。オンデマンドで次の番号を生成するだけです。ステートマシンを使用したこの遅延シーケンス生成には、多くの用途があります。それらのほとんどは関数型プログラミングの概念でカバーされています。
私の意見では、コレクションを列挙する方法としてイテレータを検討している場合(これは、最も単純なユースケースの1つにすぎません)、間違った方向に進んでいます。私が言ったように、イテレータはシーケンスを返すための手段です。シーケンスは無限でさえあるかもしれません。無限の長さのリストを返し、最初の100項目を使用する方法はありません。それは時々怠惰でなければなりません。コレクションを返すことは、コレクションジェネレーター(イテレーターとは何か)を返すこととはかなり異なります。リンゴとオレンジを比較しています。
架空の例:
static IEnumerable<int> GetPrimeNumbers() {
for (int num = 2; ; ++num)
if (IsPrime(num))
yield return num;
}
static void Main() {
foreach (var i in GetPrimeNumbers())
if (i < 10000)
Console.WriteLine(i);
else
break;
}
この例では、10000未満の素数を出力します。素数生成アルゴリズムにまったく触れることなく、100万未満の素数を出力するように簡単に変更できます。この例では、シーケンスが無限であり、消費者は最初から必要なアイテムの数さえ知らないため、すべての素数のリストを返すことはできません。
ここでの細かい答えyield returnは、リストを作成する必要がないという利点があることを示唆しています。リストは高額になる可能性があります。(また、しばらくすると、それらはかさばり、エレガントではなくなります。)
しかし、リストがない場合はどうなりますか?
yield returnさまざまな方法でデータ構造(必ずしもリストである必要はありません)をトラバースできます。たとえば、オブジェクトがツリーの場合、他のリストを作成したり、基になるデータ構造を変更したりすることなく、ノードを事前または事後の順序でトラバースできます。
public IEnumerable<T> InOrder()
{
foreach (T k in kids)
foreach (T n in k.InOrder())
yield return n;
yield return (T) this;
}
public IEnumerable<T> PreOrder()
{
yield return (T) this;
foreach (T k in kids)
foreach (T n in k.PreOrder())
yield return n;
}
yield!はF #と同じ方法で実装する必要があるため、すべてのforeachステートメントが必要になるわけではありません。
yield return:それが効率的または非効率的なコードをいつ生成するかはしばしば明らかではありません。がyield return再帰的に使用することができ、このような使用は、深くネストされた列挙子の処理に大きなオーバーヘッドを課します。手動の状態管理はコーディングがより複雑になる場合がありますが、はるかに効率的に実行されます。
「yieldreturn」イテレータブロックは、実際にその特定の結果を呼び出すまで、コードを実行しません。これは、それらを効率的に連鎖させることもできることを意味します。ポップクイズ:次のコードはファイルを何回繰り返しますか?
var query = File.ReadLines(@"C:\MyFile.txt")
.Where(l => l.Contains("search text") )
.Select(l => int.Parse(l.SubString(5,8))
.Where(i => i > 10 );
int sum=0;
foreach (int value in query)
{
sum += value;
}
答えは正確に1つであり、それはforeachループのずっと下までありません。3つの別々のlinq演算子関数がありますが、ファイルの内容をループするのは1回だけです。
これには、パフォーマンス以外の利点があります。たとえば、ログファイルを一度読み取って事前にフィルタリングし、同じメソッドをいくつかの異なる場所で使用して、使用するたびに異なるフィルターを追加する、非常に単純で一般的なメソッドを作成できます。したがって、コードを効率的に再利用しながら、良好なパフォーマンスを維持します。
良い例については、この質問に対する私の回答を参照してください:
エラーを返すC#フィボナッチ関数
基本的に、(少なくともMaxIntに到達する前に)停止しないイテレータブロックを使用してフィボナッチ数列を実装し、その実装を安全な方法で使用します。
上記のファイルの例を再度使用すると、ファイルを読み取るコードを、実際に結果を解析するコードから不要な行を除外するコードから簡単に分離できます。その最初のものは、特に、非常に再利用可能です。
これは、単純なビジュアルを持っている人よりも散文で説明するのがはるかに難しいことの1つです1:

画像が表示されない場合は、同じコードの2つのバージョンが表示され、さまざまな懸念事項の背景が強調表示されています。linqコードではすべての色が適切にグループ化されていますが、従来の命令型コードでは色が混在しています。著者は、この結果はlinqを使用する場合と命令型コードを使用する場合の典型的な結果であると主張しています(そして私は同意します)... linqは、セクション間のフローを改善するためにコードをより適切に編成します。
1これが元のソースであると思います:https: //twitter.com/mariofusco/status/571999216039542784。また、このコードはJavaですが、C#も同様であることに注意してください。
返す必要のあるシーケンスが大きすぎてメモリに収まらない場合があります。たとえば、約3か月前、MSSLQデータベース間のデータ移行プロジェクトに参加しました。データはXML形式でエクスポートされました。歩留まりの戻りは、XmlReaderで非常に役立つことが判明しました。プログラミングが非常に簡単になりました。たとえば、ファイルに1000個のCustomer要素があるとします。このファイルをメモリに読み込んだ場合、それらが順番に処理されている場合でも、すべてを同時にメモリに保存する必要があります。したがって、コレクションを1つずつトラバースするためにイテレーターを使用できます。その場合、1つの要素にメモリだけを費やす必要があります。
結局のところ、プロジェクトにXmlReaderを使用することが、アプリケーションを機能させる唯一の方法でした。長い間機能しましたが、少なくともシステム全体がハングすることはなく、OutOfMemoryExceptionが発生することもありませんでした。もちろん、yieldイテレータなしでXmlReaderを使用できます。しかし、イテレーターは私の人生をはるかに楽にしてくれました(インポート用のコードをそれほど速く、問題なく書くことはしませんでした)。このページを見て、(無限シーケンスの科学だけでなく)実際の問題を解決するためにyieldイテレータがどのように使用されているかを確認してください。
おもちゃ/デモンストレーションのシナリオでは、大きな違いはありません。ただし、イテレータを生成すると便利な場合があります。リスト全体が利用できない場合(ストリームなど)や、リストの計算コストが高く、全体が必要になる可能性が低い場合があります。
を使用yield returnすると、リストを作成しなくてもアイテムを反復処理できます。リストは必要ないが、アイテムのセットを繰り返し処理したい場合は、簡単に記述できます。
foreach (var foo in GetSomeFoos()) {
operate on foo
}
より
foreach (var foo in AllFoos) {
if (some case where we do want to operate on foo) {
operate on foo
} else if (another case) {
operate on foo
}
}
イールドリターンを使用して、メソッド内でfooを操作するかどうかを決定するためのすべてのロジックを配置でき、foreachループをはるかに簡潔にすることができます。
これは、まったく同じ質問に対する以前に受け入れられた回答です。
イテレータメソッドを調べる別の方法は、アルゴリズムを「裏返し」にするという大変な作業を行うことです。パーサーについて考えてみましょう。ストリームからテキストを取得し、その中のパターンを探して、コンテンツの高レベルの論理記述を生成します。
これで、SAXアプローチを採用することで、パーサーの作成者としてこれを簡単に行うことができます。このアプローチでは、パターンの次の部分が見つかるたびに通知するコールバックインターフェイスがあります。したがって、SAXの場合、要素の開始を見つけるたびに、beginElementメソッドを呼び出します。
しかし、これは私のユーザーに問題を引き起こします。ハンドラーインターフェイスを実装する必要があるため、コールバックメソッドに応答するステートマシンクラスを作成する必要があります。これを正しく理解するのは難しいので、最も簡単な方法は、DOMツリーを構築するストック実装を使用することです。そうすれば、ツリーを歩くことができるという便利さが得られます。しかし、その後、構造全体がメモリにバッファリングされます-良くありません。
しかし、代わりに、パーサーをイテレーターメソッドとして作成するのはどうでしょうか。
IEnumerable<LanguageElement> Parse(Stream stream)
{
// imperative code that pulls from the stream and occasionally
// does things like:
yield return new BeginStatement("if");
// and so on...
}
これは、コールバックインターフェイスアプローチよりも書くのが難しくありませんLanguageElement。コールバックメソッドを呼び出す代わりに、基本クラスから派生したオブジェクトを返すだけです。
ユーザーはforeachを使用してパーサーの出力をループできるようになったため、非常に便利な命令型プログラミングインターフェイスを利用できます。
その結果、カスタムAPIの両側が制御されているように見えるため、記述と理解が容易になります。
イールドを使用する基本的な理由は、それ自体でリストを生成/返すことです。返されたリストを使用して、さらに反復することができます。
return yieldすると、リストは生成されません。リスト内の次の項目のみが生成され、要求された場合(繰り返し)にのみ生成されます。