yield returnを使わずにIEnumerable<T>を生成する
やりたいこと
yield return
を使わずに、かつなるべく楽して IEnumerable<T>
を返すメソッドを実装したい
本題
C#、で、例えばこんな感じの拡張メソッドを定義すると、これをLinq to Objectのメソッドチェーンの中で利用することができる。
/// <summary>
/// シーケンスから、同一のキーを持つ要素の重複を取り除く
/// </summary>
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector)
{
var done = new HashSet<TKey>();
foreach (var item in source)
{
if (done.Add(keySelector(item)))
yield return item;
}
}
これと同じことを yield return
を使わずに実現しようと思うと、正攻法では IEnumerable<T>
と IEnumerator<T>
を実装したクラスをそれぞれ用意して、という感じで結構面倒くさい。
だったらこんな感じにすればいいんじゃないの?と思ってやってみると、
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector)
{
var done = new HashSet<TKey>();
return source.Where(x => done.Add(keySelector(x)));
}
一見うまくいくんだけど、下の例みたいに生成された IEnumerable<T>
を複数回列挙すると2回目以降の結果がおかしくなる。
こういう使い方をするのはレアなケースではあるのだけれど。
[Test]
public void EnumerateTwice()
{
var ret = new [] {1, 2, 3, 1, 2}.DistinctBy(x => x);
Assert.That(ret.ToArray(), Is.EqualTo(new [] {1, 2, 3})); // PASS
Assert.That(ret.ToArray(), Is.EqualTo(new [] {1, 2, 3})); // FAIL ret.ToArray() は空の配列になる
}
要は初期化(この場合はHashSetの生成)処理をメソッドチェーンの外でやってるのが問題なので、これをメソッドチェーンに埋め込んでしまえばいい訳で、こんな感じに変えてやると意図したとおりに動くようになる。
public static IEnumerable<T> DistinctBy<T, TKey>(this IEnumerable<T> source, Func<T, TKey> keySelector)
{
return new[] { source }.SelectMany(s =>
{
var done = new HashSet<TKey>();
return s.Where(x => done.Add(keySelector(x)));
});
}
別の例
/// <summary>
/// シーケンスを指定されたサイズごとに分割する.
/// [1, 2, 3, 4, 5, 6, 7].EachSlice(3) -> [1, 2, 3], [4, 5, 6], [7]
/// </summary>
public static IEnumerable<T[]> EachSlice<T>(this IEnumerable<T> source, int size)
{
return new[] { source }.SelectMany(s =>
{
var r = Enumerable.Repeat(s.GetEnumerator(), size).ToArray();
return Enumerable.Repeat(0, int.MaxValue)
.Select(_ => r.TakeWhile(e => e.MoveNext()).Select(e => e.Current).ToArray())
.TakeWhile(arr => arr.Length > 0);
});
}
おとなしくyield使えばいいんじゃないの?
デスヨネー
Visual Studio 2010でVBを書くとかそういう縛りプレイを強いられてる時にはそれなりに役に立つ気がする。
C#なら、まあyield使いますよね。はい。
Author And Source
この問題について(yield returnを使わずにIEnumerable<T>を生成する), 我々は、より多くの情報をここで見つけました https://qiita.com/wonderful_panda/items/8c2ccc2adfda516bc7e3著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .