データの「変換(射影)」
C#で配列やList<T>などのコレクションを扱う際、「すべての数値を2倍にしたい」「オブジェクトのリストから、特定のプロパティだけを抜き出したい」「計算結果を含む新しい形式のデータを作りたい」といった変換処理は頻繁に発生します。
LINQ(Language Integrated Query)のSelectメソッドは、コレクションの各要素に対して指定した変換関数を適用し、その結果を新しいシーケンスとして返すためのメソッドです。データベース用語では「射影(Projection)」とも呼ばれます。
この記事では、Selectメソッドの基本的な使い方から、匿名型を利用したデータ構造の変換、そしてインデックス(要素番号)を利用した処理について解説します。
Selectメソッドの基本:値の変換
Selectメソッドは、ラムダ式 x => ... を引数に取り、コレクションの各要素 x をどのように変換するかを定義します。入力と出力のデータ型は異なっていても構いません(例:int を string に変換するなど)。
コード例:数値の加工
整数の配列に対し、それぞれの値を2乗(自分自身を掛ける)した新しいシーケンスを生成する例です。
using System;
using System.Collections.Generic;
using System.Linq; // LINQを使用するために必須
public class SelectBasicExample
{
public static void Main()
{
// 元のデータ(整数配列)
var numbers = new[] { 2, 4, 6, 8, 10 };
// 各要素を 2乗 する変換処理
// 入力(int) -> 出力(int)
var squaredNumbers = numbers.Select(n => n * n);
Console.WriteLine($"元の値: {string.Join(", ", numbers)}");
Console.WriteLine($"変換後: {string.Join(", ", squaredNumbers)}");
// 文字列への変換例
// 入力(int) -> 出力(string)
var stringNumbers = numbers.Select(n => $"No.{n}");
Console.WriteLine($"文字列: {string.Join(", ", stringNumbers)}");
}
}
出力結果:
元の値: 2, 4, 6, 8, 10
変換後: 4, 16, 36, 64, 100
文字列: No.2, No.4, No.6, No.8, No.10
応用:匿名型を利用した構造の変換
Selectメソッドが真価を発揮するのは、オブジェクトのリストから必要な情報だけを抽出・加工して、新しい「形」を作る場合です。
この際、わざわざ結果用のクラスを定義しなくても、「匿名型(Anonymous Type)」を使用することで、その場限りのデータ構造を定義して返すことができます。
コード例:計算結果を含む新しいオブジェクトの生成
商品データ(単価と数量)を持つクラスのリストから、商品名と「合計金額」を持つ新しいリストを生成します。
using System;
using System.Collections.Generic;
using System.Linq;
// 商品クラス
public class ProductItem
{
public string Name { get; set; }
public int UnitPrice { get; set; }
public int Quantity { get; set; }
}
public class SelectProjectionExample
{
public static void Main()
{
var cartItems = new List<ProductItem>
{
new ProductItem { Name = "Coffee Beans", UnitPrice = 1500, Quantity = 2 },
new ProductItem { Name = "Filter Papers", UnitPrice = 500, Quantity = 1 },
new ProductItem { Name = "Coffee Mill", UnitPrice = 3000, Quantity = 1 }
};
// Select を使って、必要な情報だけを持つ「匿名型」に変換(射影)する
var receipts = cartItems.Select(item => new
{
item.Name, // プロパティ名を省略すると元の名前が使われる
TotalPrice = item.UnitPrice * item.Quantity, // 計算結果に新しい名前を付ける
IsExpensive = item.UnitPrice >= 2000 // 条件判定の結果も含められる
});
Console.WriteLine("--- 注文明細 ---");
// 変換された匿名型のリストを処理
foreach (var receipt in receipts)
{
string note = receipt.IsExpensive ? " [高級品]" : "";
Console.WriteLine($"{receipt.Name}: {receipt.TotalPrice:C}{note}");
}
}
}
出力結果:
--- 注文明細 ---
Coffee Beans: ¥3,000
Filter Papers: ¥500
Coffee Mill: ¥3,000 [高級品]
このように、元のクラス(ProductItem)には存在しないTotalPriceやIsExpensiveといったプロパティを、Selectの中で動的に生成できます。
インデックス(要素番号)を利用した変換
Selectメソッドには、要素そのものだけでなく、その要素の**インデックス(何番目の要素か)**を使用できるオーバーロード(別バージョンのメソッド)が存在します。
構文: collection.Select((item, index) => ...)
item: 現在の要素index: 現在の要素のインデックス(0から始まります)
コード例:ランキング形式の文字列生成
タイムを記録した配列から、順位(インデックス + 1)付きの文字列を生成する例です。
using System;
using System.Linq;
public class SelectIndexExample
{
public static void Main()
{
// レースのタイム結果(速い順に並んでいると仮定)
var lapTimes = new[] { 45.2, 46.8, 47.1, 48.5 };
// 第2引数でインデックスを受け取る
var ranking = lapTimes.Select((time, index) =>
{
int rank = index + 1; // 0始まりなので +1 する
return $"{rank}位: {time:F1}秒";
});
Console.WriteLine(string.Join(Environment.NewLine, ranking));
}
}
出力結果:
1位: 45.2秒
2位: 46.8秒
3位: 47.1秒
4位: 48.5秒
まとめ
LINQのSelectメソッドは、コレクションの各要素を新しい形に変換するための強力なツールです。
- 値の変換: 計算や型変換を行います。
- 構造の変換(射影):
new { ... }(匿名型)と組み合わせることで、必要なプロパティだけを抽出したり、計算結果を含む新しいオブジェクトを作成したりできます。 - インデックスの利用:
(item, index) => ...の形式を使えば、要素の順番に基づいた処理も記述可能です。
Whereメソッド(フィルタリング)と並んで、LINQで最も頻繁に使用される重要なメソッドの一つです。
