- はじめに:「インデックス付き Select」は“要素の値+位置情報”を同時に扱える便利ワザ
- インデックス付き Select の基本:Select((value, index) => …)
- 実務でよくある使い方 1:行番号を付ける
- 実務でよくある使い方 2:インデックスを使ってフィルタリング
- 実務でよくある使い方 3:インデックスをキーにして別データと突き合わせる
- 実務でよくある使い方 4:エラー時に「何番目のデータか」をログに残す
- インデックス付き Select の注意点
- インデックス付き Select をもっと使いやすくするユーティリティ
- まとめ:「インデックス付き Select」は“位置情報を自然に扱うための道具”
はじめに:「インデックス付き Select」は“要素の値+位置情報”を同時に扱える便利ワザ
LINQ の Select は「値を変換する」ためのメソッドですが、
実務では「値だけでなく、その要素が何番目か(インデックス)も使いたい」場面がよくあります。
「行番号を付けたい」
「奇数番目だけ処理したい」
「インデックスをキーにして別データと突き合わせたい」
「CSV の 1 行目はヘッダー扱いにしたい」
こういうときに使えるのが インデックス付き Select です。
LINQ の Select には、実は 2 つの引数を取るオーバーロード があり、
Select((value, index) => ...)
C#という形で「値」と「インデックス」を同時に扱えます。
ここから、初心者向けに
インデックス付き Select の基本
実務でよくある使い方
インデックスの注意点(0 始まり・順序保証)
ユーティリティ化してもっと使いやすくする方法
を、例題付きで丁寧に解説していきます。
インデックス付き Select の基本:Select((value, index) => …)
まずは動きをコードで確認する
using System;
using System.Linq;
var names = new[] { "Alice", "Bob", "Charlie" };
var result = names
.Select((name, index) => $"{index}: {name}");
foreach (var line in result)
{
Console.WriteLine(line);
}
C#出力はこうなります。
0: Alice
0: Bob
2: Charlie
ここでの重要ポイントは、
インデックスは 0 始まり
Select の中で「値」と「インデックス」を同時に使える
ということです。
実務でよくある使い方 1:行番号を付ける
CSV やログの行番号を付けたいとき
var lines = File.ReadAllLines("data.csv");
var numbered = lines
.Select((line, index) => new
{
LineNumber = index + 1, // 1 始まりにしたい場合
Text = line
});
foreach (var x in numbered)
{
Console.WriteLine($"{x.LineNumber}: {x.Text}");
}
C#業務では「1 行目はヘッダー」「2 行目以降がデータ」という扱いが多いので、index + 1 として 1 始まりにするのが定番です。
ここでの重要ポイントは、
「行番号を付ける」という処理を、LINQ の中で自然に書ける
ということです。
実務でよくある使い方 2:インデックスを使ってフィルタリング
偶数行だけ処理したい/奇数行だけ処理したい
var evenLines = lines
.Select((line, index) => new { line, index })
.Where(x => x.index % 2 == 0)
.Select(x => x.line);
C#ここでの重要ポイントは、
インデックスを使った条件分岐が簡単に書ける
ということです。
実務でよくある使い方 3:インデックスをキーにして別データと突き合わせる
例:2 つのリストを「位置」でマージする
var keys = new[] { "A", "B", "C" };
var values = new[] { 10, 20, 30 };
var merged = keys
.Select((key, index) => new
{
Key = key,
Value = values[index]
});
C#ここでの重要ポイントは、
「同じ位置にあるデータ同士を結びつける」処理が簡単に書ける
ということです。
実務でよくある使い方 4:エラー時に「何番目のデータか」をログに残す
例:データ変換中に例外が出たら「何行目か」を記録したい
var converted = lines
.Select((line, index) =>
{
try
{
return ConvertLine(line);
}
catch (Exception ex)
{
throw new Exception($"Error at line {index + 1}: {ex.Message}");
}
})
.ToList();
C#ここでの重要ポイントは、
インデックスがあると、エラーの特定が圧倒的に楽になる
ということです。
インデックス付き Select の注意点
インデックスは「0 始まり」
LINQ のインデックスは必ず 0 始まりです。
1 始まりにしたい場合は index + 1 を使います。
並列 LINQ(AsParallel)ではインデックスが保証されない
var result = items
.AsParallel()
.Select((value, index) => ...)
C#これは 危険 です。
PLINQ(並列 LINQ)は順序を保証しないため、
インデックスが期待通りの順番にならないことがあります。
順序が必要なら必ず
.AsParallel()
.AsOrdered()
C#を付ける必要があります。
ここでの重要ポイントは、
インデックス付き Select は「順次処理」で使うのが基本
ということです。
インデックス付き Select をもっと使いやすくするユーティリティ
「Index()」のような拡張メソッドを作る
毎回 (value, index) と書くのが面倒なら、
「インデックスを付けるだけのユーティリティ」を作ると便利です。
public static class IndexExtensions
{
public static IEnumerable<(T item, int index)> WithIndex<T>(
this IEnumerable<T> source)
{
return source.Select((item, index) => (item, index));
}
}
C#使い方はこうです。
var result = names.WithIndex();
foreach (var (name, index) in result)
{
Console.WriteLine($"{index}: {name}");
}
C#ここでの重要ポイントは、
タプルを使うと、インデックス付きデータがとても扱いやすくなる
ということです。
まとめ:「インデックス付き Select」は“位置情報を自然に扱うための道具”
インデックス付き Select の本質は、
「値」と「位置」を同時に扱えるようにすることで、
業務ロジックをシンプルに書けるようにする
ことです。
押さえておきたいポイントをまとめると、
Select((value, index) => ...) でインデックスが使える
行番号付け・奇数行/偶数行処理・エラーログなど実務で大活躍
インデックスは 0 始まり(1 始まりにしたいなら index + 1)
並列 LINQ ではインデックスが保証されないので注意
タプルを返す WithIndex() 拡張メソッドを作るとさらに便利
ここまで理解できれば、
「インデックスが必要な処理」を for 文で書く必要がなくなり、
LINQ のパイプラインの中で自然に表現できるようになります。
