2013年5月3日金曜日

Enumerable.Zipメソッド

2つのコレクションを、同時に列挙したいケースというのはたまにありますよね。たとえば、2つの異なるデータソース上のデータが、何らかのキーでJoinできるわけではなく、単純に格納順序で順番に取り出してなんかしらの処理をして出力したい。とか。

まぁ、こんなケースはたいていデザイン的に問題があったりするんですけど、とはいえリファクタリングするにはなんだか大掛かりになりすぎちゃって、「そこまでしたくないなぁ。」なとき。

こうなると、foreachで2つのコレクションから順次取り出すことはできないので、for文+インデクサを使ったりするわけですが、こんな感じのコードになりますね。

int[] numbers = new[] { 0, 1, 2, 3, 4 };
string[] names = new[] { "Zero", "One", "Two", "Three", "Four" };

for (int i = 0; i < numbers.Length; ++i)
{
  Console.WriteLine("{0}:{1}", numbers[i], names[i]);
}

ところが、今となっては単純な列挙にforとかiとか[]とか、こういうのを見るとなんだか可読性が悪い(…様な)気がして、foreachで置き変えたくなっちゃう。こんなときに使えるのが「Enumerable.Zip」メソッドです。2つのコレクションを、まとめて一つにしちゃってくれる、「.net4」で追加されたメソッドです。たとえば、Tupleと組み合わせることで、こんなコードに置き換えできる。

int[] numbers = new[] { 0, 1, 2, 3, 4 };
string[] names = new[] { "Zero", "One", "Two", "Three", "Four" };

foreach (var x in numbers.Zip(names, Tuple.Create))
{
  Console.WriteLine("{0}:{1}", x.Item1, x.Item2);
}

やっぱりこっちのほうがすっきり見えるわけです。完全にLINQに毒されてますな。自覚あります。

Item1とかItem2とか、このコードくらいスコープが短ければ気にならないけど、もう少し複雑な処理で、名前にも気を使いたいような場合なら、Tupleの代わりに匿名クラスを使うことで、こうも書けますね。

int[] numbers = new[] { 0, 1, 2, 3, 4 };
string[] names = new[] { "Zero", "One", "Two", "Three", "Four" };

foreach (var x in numbers.Zip(names, (number, name) => new { number, name }))
{
  Console.WriteLine("{0}:{1}", x.number, x.name);
}

どちらでも、ケースバイケースで。あるいはお好みで。

0 件のコメント:

コメントを投稿