LINQは便利です。LINQが使える環境なら率先して使いたいところです。LINQに出会ってからは、LINQに依存し過ぎてLINQがないと生きられない体になってしまいました。LINQさん、愛してます...///
と、くだらない前フリはさておき、LINQを使っていて思うことは「あってほしいメソッドが (稀に) ない」ということです。その多くはInteractive Extensions (Ix)を利用すれば何とかなるのですが、それでも他にも「あるといいのになー」と思うものはあります。それにIxはまだExperimental (実験版) リリースで、仕事で採用するにはそれだけでお許しが...。ということで、あると地味ぃに便利そうなものをいくつか紹介します。
ForEachメソッド
言わずと知れたForEachメソッド。LINQやったことある方なら、一度は作ったことあるんじゃないでしょうか。
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action) { foreach (var item in source) action(item); }
「なんでお前は標準搭載じゃないんだ!(クワッ」と誰もが思うところでしょうが、C#コンパイラチームのEricさんのブログ記事によると、「副作用を避けるため」とか言う理由で搭載が見送られているようです。「じゃあなんでIxには搭載したんだよ」などと言いたくもなりますが、ないものは仕方ないので、Ixが使えない場合はサクッと作りましょう。
Convertメソッド
先日@shibayanがこんなクイズを出してました。標準のLINQを使うのであればこれより短い書き方はないと思われますが、CastしてからSelectって比較的あるケースだと思うのに1メソッドで書けません。ということで、作ったのがこちら。
public static IEnumerable<T> Convert<T>(this IEnumerable source, Func<object, T> converter) { foreach (var item in source) yield return converter(item); }
まさにCastとSelectのコンビネーション。メソッド名をCastにするかSelectにするか悩んだのですが、WhereとCastを合体させた感のある機能にOfTypeメソッドというのがあるので、「じゃあこっちも変えましょうか」とConvertにしました。
これがあれば、前出のクイズはもう少し文字数少なく書けますね。
Concatメソッド
稀にありますよね、単一要素だけを繋げたいとき。そんなときは、例えばこう書きます。
var query = Enumerable.Range(0, 3).Concat(new []{ 3 });
Concatの引数がIEnumerable<T>しか受け付けないので、単一要素なのを配列に変えたりして対応します。が、「そもそも可変長引数に対応していれば問題ない」という考えを持ち出して作ったのがこちら。
public static IEnumerable<T> Concat<T>(this IEnumerable<T> first, params T[] second) { //--- first.Concat(second)って書くとStackOverflowException!! return Enumerable.Concat(first, second); }
これで少し気持ち良くなれる気がします。
Chunkメソッド
「要素をいくつか毎に纏めたい」という要望も稀にあると思います。そういった場合はコレを使いましょう。実装自体もLINQを使うと簡単にできちゃって素晴らしいですね。
public static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int count) { var result = new List<T>(count); foreach (var item in source) { result.Add(item); if (result.Count == count) { yield return result; result = new List<T>(count); } } if (result.Count != 0) yield return result.ToArray(); //--- 何度も回って効率が悪いという@neuecc様のご指摘により廃版 /* while (source.Any()) { yield return source.Take(count); source = source.Skip(count); } */ }
あと、IxのBufferというメソッドが同じ機能のようなので、メソッド名をChunkでなくてBufferというようにした方がいいというご指摘もありました。その通りですね!
Indexedメソッド
意識の高いLINQ星人たちは「for文を使ったら負け」みたいな気持ちになると聞きます。僕はちっともLINQ星人じゃないですが、そう思ってしまうことはあります。でも、「for文は書きたくないけど要素のインデックスは欲しい」などというワガママもあり、そういうときはインデックス付きで要素を返すSelect文を使います。例えばこんな感じ。
source
.Select((x, i) => new { Element = x, Index = i })
.DoSometing(...);
特に難しくはないですが、地味にめんどくさい。ということで、インデックス付きの型に変換してしまえ作戦。
//--- 要素とインデックスを格納するクラス public class IndexedItem<T> { public T Element{ get; private set; } public int Index{ get; private set; } public IndexedItem(T element, int index) { this.Element = element; this.Index = index; } } //--- クラス生成をラップ public static IEnumerable<IndexedItem<T>> Indexed<T>(this IEnumerable<T> source) { return source.Select((x, i) => new IndexedItem<T>(x, i)); }
これで簡単にインデックス付き要素を取得できます。めんどくさい定型句とはお別れです。
使用例
今回の紹介したメソッドを無理やり (?) 全部使ってみると、以下のような感じになります。地味ぃに楽になった気がしますね。
(Enumerable.Range(1, 7) as IEnumerable) //--- わざとIEnumerableに変換 .Convert(x => x.ToString() + "日目") //--- 一気に文字列に変換 .Concat("I", "Love", "LINQ") //--- 可変長引数の指定OK .Chunk(3) //--- 3要素ずつまとめる .Indexed() //--- インデックス付加 .ForEach(x => //--- foreachをメソッドチェインで記述 { Console.WriteLine("Index: {0}", x.Index); x.Element.ForEach(y => Console.WriteLine("\\t{0}", y)); }); /* Index: 0 1日目 2日目 3日目 Index: 1 4日目 5日目 6日目 Index: 2 7日目 I Love Index: 3 LINQ */
何よりもIxの正式版の公開や.NET標準への追加搭載があるのが嬉しいですが、それ以外の独自機能もこのようにちょっとずつ作って行くと、LINQがますます便利になってもっと素敵なコーディングができることと思います。LINQで使えるメソッドをTipsとして集めたサイトでもオープンしないかなー。そしたらみんなHappyになれる予感!
Enjoy LINQ more and more!!