このコードを考えます:
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
キーワードが何をするのかを理解しています。特定の種類のもの(イテレーター)をビルドするようコンパイラーに指示します。しかし、なぜそれを使用するのですか?コードがわずかに少ないこととは別に、それは私にとって何をしますか?
yield
を使用すると、コレクションが作成されますlazy。
最初の5つのアイテムだけが必要だとしましょう。最初の5つの項目を取得するには、リスト全体をループする必要があります。 yield
を使用すると、最初の5つの項目のみをループします。
イテレータブロックの利点は、遅延動作することです。したがって、次のようなフィルタリングメソッドを記述できます。
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;
}
}
}
これにより、好きなだけストリームをフィルタリングでき、一度に複数のアイテムをバッファリングすることはありません。たとえば、返されたシーケンスの最初の値のみが必要な場合、なぜeverythingを新しいリストにコピーするのでしょうか?
別の例として、イテレータブロックを使用してinfiniteストリームを簡単に作成できます。たとえば、乱数のシーケンスは次のとおりです。
public static IEnumerable<int> RandomSequence(int minInclusive, int maxExclusive)
{
Random rng = new Random();
while (true)
{
yield return rng.Next(minInclusive, maxExclusive);
}
}
リストに無限シーケンスをどのように保存しますか?
私の Edulinqブログシリーズ は、反復ブロックをheavy使用するLINQのサンプル実装を提供します。 LINQは基本的に怠zyであり、リストに物事を置くことは、そのようには機能しません。
「リスト」コードでは、次のステップに渡す前に、完全なリストを処理する必要があります。 「yield」バージョンは、処理されたアイテムをすぐに次のステップに渡します。その「次のステップ」に「.Take(10)」が含まれている場合、「yield」バージョンは最初の10個のアイテムのみを処理し、残りは忘れます。 「リスト」コードはすべてを処理します。
つまり、多くの処理を行う必要がある場合や、処理するアイテムのリストが長い場合に、最も大きな違いが見られます。
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
...等。キャンセルされるまでコンソールに。
object jamesItem = null;
foreach(var item in FilteredList())
{
if (item.Name == "James")
{
jamesItem = item;
break;
}
}
return jamesItem;
上記のコードを使用してFilteredList()をループし、item.Name == "James"がリストの2番目の項目で満たされると仮定すると、yield
を使用するメソッドは2回生成されます。これは怠zyな動作です。
Listを使用するメソッドがn個のオブジェクトをすべてリストに追加し、呼び出し元のメソッドに完全なリストを渡す場合。
これは、IEnumerableとIListの違いを強調できるユースケースです。
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拡張機能を使用して必要なものだけを照会できるため、素晴らしいです。
[yield]を使用する理由コードがわずかに少ないこととは別に、それは私にとって何をしますか?
役に立つ場合もあれば、役に立たない場合もあります。データセット全体を調べて返さなければならない場合、yieldを使用してもメリットはありません。オーバーヘッドが発生するためです。
Yieldが本当に輝くのは、部分セットのみが返されるときです。最良の例はソートです。今年の日付と金額を含むオブジェクトのリストがあり、その年の最初の(5)レコードを表示するとします。
これを実現するには、リストを日付の昇順で並べ替えてから、最初の5つを取得する必要があります。これがyieldなしで行われた場合、最後の2つの日付が正しいことを確認するまで、全体リストをソートする必要があります。
ただし、収量では、最初の5つのアイテムが確立されると、ソートが停止し、結果が利用可能になります。これにより、時間を大幅に節約できます。
Yield returnステートメントでは、一度に1つのアイテムのみを返すことができます。リスト内のすべてのアイテムを収集し、再びそのリストを返します。これはメモリのオーバーヘッドです。