foreach
ステートメントで、インデックス付きで列挙したいことが時々あります。
今回は、そういうときの対処方法について。
というか、C# 7が待ち遠しくなる話。
配列やList<T>
であれば以下のようにも書けます。
for (int i = 0; i < length; i++)
{
var item = array[i];
Console.WriteLine($"index: {i}, value: {item}");
}
IEnumerable<T>
の場合にはこうは書けず、
現状だと、以下のようにループの外側に1個変数を作る必要があったりします。
var i = 0;
foreach (var item in items)
{
Console.WriteLine($"index: {i}, value: {item}");
i++;
}
ループの外側に変数i
が漏れるのが嫌なのと、
あと、continue
が絡むとi++
するのが大変になったりします。
Select
のオーバーロードの1つを使って、以下のような書き方も一応できます。
foreach (var x in items.Select((item, index) => new { item, index }))
{
Console.WriteLine($"index: {x.index}, value: {x.item}");
}
ただ、これだと無駄にオブジェクトがnew
されます(匿名型は参照型なのでヒープ確保が発生します)。ループの中でのヒープ確保はできれば避けたい負担です。
それに、x.item
みたいな書き方がちょっと嫌な感じです。
C# 7であれば、タプルを使うのがいいかもしれません。ついでに、分解構文も使えば多少すっきりします。
foreach (var (item, index) in items.Select((item, index) => (item, index)))
{
Console.WriteLine($"index: {index}, value: {item}");
}
タプルは値型なので、いくらかヒープ確保が減ります。
また、分解があるおかげでx.
とか書く必要がなくなりました。
でもまだちょっとうっとおしいですね。
(item, index) => (item, index)
とか毎度書きたくないです。
拡張メソッドを用意しておきたいところ。
public static partial class TupleEnumerable
{
public static IEnumerable<(T item, int index)> Indexed<T>(this IEnumerable<T> source)
{
if (source == null) throw new ArgumentNullException(nameof(source));
IEnumerable<(T item, int index)> impl()
{
var i = 0;
foreach (var item in source)
{
yield return (item, i);
++i;
}
}
return impl();
}
}
これで、以下のように書けます。
foreach (var (item, index) in items.Indexed())
{
Console.WriteLine($"index: {index}, value: {item}");
}
これなら、まあ、悪くはなさそうです。 こういうメソッド、そこそこ使うことがありそう。
ちなみに、今回はイテレーターを使ってIndexed
メソッドを実装しましたが、ガチガチに最適化するなら、以下のように、構造体で実装してヒープ確保をなくすべきかもしれません。