異なる2つの配列やリストがあり、それぞれの「同じインデックスにある要素」をペアとして扱いたい場合、forループを使用してインデックス管理を行うのは記述が冗長になりがちです。
C#のLINQにあるZipメソッドを使用すると、ファスナー(Zipper)を閉じるように2つのシーケンスをきれいに噛み合わせ、ペアのシーケンスを作成することができます。今回は、.NET 6以降で利用可能な「タプルを返すモダンな記述」を中心に、学生名とテストの点数を結合して成績表を作成する例を解説します。
Zipメソッドの概要
Zipメソッドは、2つのシーケンスを先頭から順に要素同士を組み合わせます。
- .NET 6以降: 引数にシーケンスを1つ渡すだけで、要素をペアにした
ValueTuple((T1, T2))のシーケンスを返します。 - .NET Framework / 古い.NET Core: 第2引数に「結合結果をどう生成するか」を指定する関数(
resultSelector)が必要です。
今回は、より簡潔に書ける.NET 6以降のスタイルを推奨します。
実践的なコード例:学生名と点数の紐付け
以下のコードは、「学生のリスト」と「点数のリスト」という独立した2つのデータソースを結合し、名前と点数がペアになった成績データを作成する例です。
using System;
using System.Collections.Generic;
using System.Linq;
namespace SchoolSystem
{
class Program
{
static void Main()
{
// シナリオ:
// 学生の名前リストと、各学生のテストの点数リストが個別に存在する。
// 並び順は対応しているものとし、これらを結合して成績一覧を表示したい。
// 学生名のリスト
var students = new[] { "相田", "飯島", "上野", "江藤", "太田" };
// 点数のリスト(要素数が学生より少ない場合を想定)
var scores = new[] { 85, 92, 76, 88 };
// 1. Zipメソッドで2つのリストを結合
// .NET 6以降では、以下のように書くだけで (string, int) のタプルが生成されます。
// ※要素数が異なる場合、少ない方(ここではscoresの4件)に合わせて打ち切られます。
IEnumerable<(string Name, int Score)> reportCard = students.Zip(scores);
// 2. 結果の出力
// 分解宣言(Deconstruction)を使ってタプルの内容を取り出します。
Console.WriteLine("--- 成績一覧 ---");
foreach (var (name, score) in reportCard)
{
Console.WriteLine($"氏名: {name} | 点数: {score}点");
}
}
}
}
実行結果
--- 成績一覧 ---
氏名: 相田 | 点数: 85点
氏名: 飯島 | 点数: 92点
氏名: 上野 | 点数: 76点
氏名: 江藤 | 点数: 88点
技術的なポイントと注意点
1. 要素数が異なる場合の挙動
Zipメソッドの重要な特性として、**「要素数が少ない方のシーケンスに合わせて処理を終了する」**という点があります。 上記のコード例では、studentsは5人ですが、scoresは4つしかありません。この場合、最後の「太田」さんはペアになる点数がないため、結果のシーケンスに含まれず無視されます。例外は発生しません。
2. 古いバージョンでの記述(ResultSelector)
.NET Framework環境や、結合時にタプル以外のオブジェクト(自作クラスなど)を生成したい場合は、第2引数にセレクタ関数を指定します。
// 名前と点数を使って、文字列を生成して返す例
var messages = students.Zip(scores, (name, score) => $"{name}さんは{score}点です");
3. 3つのシーケンスを結合 (.NET 6以降)
最新の環境では、3つのシーケンスを同時に結合するオーバーロードも用意されています。
var ids = new[] { 1, 2, 3 };
var names = new[] { "A", "B", "C" };
var ages = new[] { 20, 22, 25 };
// (id, name, age) のタプルになる
var triples = ids.Zip(names, ages);
まとめ
Zipメソッドを使用することで、インデックス変数を管理するforループを書く必要がなくなり、リスト同士の結合処理を非常に直感的に記述できます。特に.NET 6以降で導入されたタプルを返すオーバーロードは強力ですので、データの突き合わせが必要な場面では積極的に活用してください。
