1つのループで複数の配列を列挙する

  • C#

複数の配列を同時にループさせて同じインデックスの値をまとめて取得したい!
みたいなケース、まれによくあると思います。
というわけでいくつか方法を紹介します。

for文

素直にfor文でループさせる方法です。
余計なことをしていないため今回紹介する方法の中で恐らく最速です。
どちらか一方の配列長が短い場合は、ループ前にLengthを比較して短いほうでループさせる必要がありますが、
今回はEnumを例にしている関係上2つの配列の長さが同じため、特に気にする必要はないですね。

【2021-08-27更新】
最速じゃありませんでした。(スミマセン)
当初、「valuesとnames、配列長同じだから条件式はどちらか片方のLengthを使えばええやろ!」と思っていたので、
条件式をi < values.Lengthとしていたのですが、これだとnames側の境界チェックが消えていませんでした...
最速にするには条件式をi < values.Length && i < names.Lengthとする必要があったようです。

「どちらか一方の配列長が短い場合は~」の方も、この条件式にすればそもそも比較する必要がなく、
配列長の短い配列に合わせてループされるようになります。

using System;

DayOfWeek[] values = Enum.GetValues<DayOfWeek>();
string[] names = Enum.GetNames<DayOfWeek>();

for (var i = 0; i < values.Length && i < names.Length; i++)
{
    DayOfWeek value = values[i];
    string name = names[i];

    Console.WriteLine($"{(int)value} : {name}");
}

// 結果
// 0 : Sunday
// 1 : Monday
// 2 : Tuesday
// 3 : Wednesday
// 4 : Thursday
// 5 : Friday
// 6 : Saturday

Enumerable.Zip

LINQのEnumerable.Zipメソッドを使った方法です。
for文を使ったパターンよりもスッキリしていて見やすいですね。
個人的に「複数の配列をまとめて処理する」と言われて最初に思い浮かぶのはこの方法です。
.NET Core 3からは要素の値をValueTupleで返すオーバーロードが追加されているので、
より使いやすくなっています。

using System;
using System.Linq;

DayOfWeek[] values = Enum.GetValues<DayOfWeek>();
string[] names = Enum.GetNames<DayOfWeek>();

// .NET Standard 1.0
foreach (var item in values.Zip(names, (value, name) => new { value, name }))
{
    Console.WriteLine($"{(int)item.value} : {item.name}");
}

// .NET Core 3
foreach ((DayOfWeek value, string name) in values.Zip(names))
{
    Console.WriteLine($"{(int)value} : {name}");
}

// 結果
// 0 : Sunday
// 1 : Monday
// 2 : Tuesday
// 3 : Wednesday
// 4 : Thursday
// 5 : Friday
// 6 : Saturday

GetEnumerator()拡張メソッド

拡張メソッドのGetEnumerator()を使う方法です。
この機能はC# 9.0から使用可能で、
拡張メソッドにGetEnumerator()を実装すればどんな型でもforeachでループできるようになります。

ここでは2つの配列(厳密にはIEnumerable<T>)のタプルにGetEnumerator()を実装しています。
なお、他の方法と違う感を出すためにIEnumerator<T>.MoveNextメソッドを使った実装にしていますが、
上記のfor文やEnumerable.Zipメソッドを使う方法をそのままラップするような実装でももちろんOKです。

using System;
using System.Collections.Generic;

DayOfWeek[] values = Enum.GetValues<DayOfWeek>();
string[] names = Enum.GetNames<DayOfWeek>();

foreach ((DayOfWeek value, string name) in (values, names))
{
    Console.WriteLine($"{(int)value} : {name}");
}

public static class TupleExtensions
{
    public static IEnumerator<(T1, T2)> GetEnumerator<T1, T2>(this (IEnumerable<T1> Source1, IEnumerable<T2> Source2) tuple)
    {
        // nullチェック等は省略
    
        using var e1 = tuple.Source1.GetEnumerator();
        using var e2 = tuple.Source2.GetEnumerator();
        
        // 長さは短い方に合わせる
        while (e1.MoveNext() && e2.MoveNext())
        {
            yield return (e1.Current, e2.Current);
        }
    }
}

// 結果
// 0 : Sunday
// 1 : Monday
// 2 : Tuesday
// 3 : Wednesday
// 4 : Thursday
// 5 : Friday
// 6 : Saturday