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メソッドを実装しましたが、ガチガチに最適化するなら、以下のように、構造体で実装してヒープ確保をなくすべきかもしれません。