1. はじめに
ドラマを観てるとき、全キャラの全情報を知るわけじゃなくて、ストーリーに必要なことだけ分かれば十分だよね。プログラミングでも似たようなことがよくある。全部のオブジェクトが欲しいんじゃなくて、特定のフィールドや値、あるいはそれを使った計算結果だけ欲しい時が多いんだ。
プロジェクションはLINQで、元のシーケンスの要素を新しい形に変換すること。普通はSelectメソッドを使うよ。Selectは、各要素に関数を適用して結果を返す魔法のマシンみたいなもんだと思ってOK。ほんと、それだけ。トリックはないよ。
プロジェクションが必要なときって?
- ユーザーの名前だけ表示したい、全部のデータはいらない。
- メール配信の準備:顧客オブジェクトからアドレスと名前だけ取り出す。
- 計算したい:商品の価格に税金を足して新しいコレクションを作る。
- UI用:全部のデータじゃなくて一部だけ表示して、画面をスッキリさせたい。
2. Selectメソッド:構文と基本の使い方
基本構文
LINQには2つのスタイルがあるよ:メソッド構文(Method Syntax)とクエリ構文(Query Syntax)。Selectはどっちでも結果は同じ。
メソッド構文
var query = myCollection.Select(x => x.何を返すか);
ここでxはコレクションの各要素で、右側は要素をどう変換するかの式だよ。
クエリ構文
var query = from x in myCollection
select x.何を返すか;
こっちはちょっとSQLっぽい感じ。
簡単な例:数字のリスト
var numbers = new List<int> { 1, 2, 3, 4, 5 };
// それぞれの2乗が欲しい
var squares = numbers.Select(n => n * n);
foreach (var s in squares)
{
Console.WriteLine(s); // 1, 4, 9, 16, 25
}
今、数字のコレクションを一瞬で2乗のコレクションに変換したよ。手動でループ書かなくてもOK!
例2
前の例でProductクラスがあったよね:
public class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
リストがあるとする:
var products = new List<Product>
{
new Product { Name = "チョコレート", Price = 120 },
new Product { Name = "チーズ", Price = 250 },
new Product { Name = "パン", Price = 55 }
};
全部のProductオブジェクトが欲しいわけじゃなくて、名前だけ欲しい場合はこう:
var names = products.Select(p => p.Name);
foreach (var name in names)
{
Console.WriteLine(name); // チョコレート, チーズ, パン
}
3. 戻り値のコレクション — どんなパターンがある?
Selectは必ずコレクション(正確にはIEnumerable<TOutput>)を返すよ。TOutputは関数の戻り値の型。自分で決めてOK:文字列でも数字でも匿名型でも新しいオブジェクトでも。
新しい型へのプロジェクション
例えば、生のオブジェクトじゃなくて「プロジェクション」が欲しい場合:
var projections = products.Select(p => new { p.Name, 税込価格 = p.Price * 1.2 });
foreach (var item in projections)
{
Console.WriteLine($"{item.Name}: {item.税込価格}");
}
ここでは、名前と仮想の消費税を加えた価格を持つ匿名オブジェクトのコレクションを作ったよ!
4. 新しいクラスや匿名型を返す
結果用にちゃんとしたクラスを作ってもいいし、「今だけ」使うなら匿名型でもOK。
匿名型
var result = products.Select(p => new { 名前 = p.Name, 旧価格 = p.Price, 新価格 = p.Price + 40 });
foreach (var p in result)
{
Console.WriteLine($"{p.名前}: 前は{p.旧価格}、今は{p.新価格}");
}
匿名型は、1回だけの結果をサクッと作りたいときに便利。クラスを増やす必要がないならこれで十分。
自作クラスでプロジェクション
新しいクラスを作る:
public class ProductDto
{
public string Name { get; set; }
public double PriceWithTax { get; set; }
}
LINQクエリの中で:
var productsWithTax = products.Select(p => new ProductDto
{
Name = p.Name,
PriceWithTax = p.Price * 1.2
});
foreach (var p in productsWithTax)
{
Console.WriteLine($"{p.Name}: {p.PriceWithTax}");
}
5. 内部コレクションやプロパティへのアクセス
もしオブジェクトの中にコレクションがあったら?例えば、ユーザーごとに注文リストがある場合。
public class User
{
public string Name { get; set; }
public List<Product> Purchases { get; set; }
}
全ユーザーの購入リストだけ取り出したい?余裕!
var allPurchases = users.Select(u => u.Purchases);
foreach (var purchaseList in allPurchases)
{
foreach (var product in purchaseList)
{
Console.WriteLine(product.Name);
}
}
重要! この場合、商品リストの「リストのリスト」になってる。全ユーザーの全商品を1つのリストにまとめたい?それは次のレクチャーのテーマだよ。そこでSelectManyを紹介するね。
6. 便利なポイント
コレクション型への変換:ToList()など
SelectはIEnumerable<T>を返す。普通のリスト(List<T>)が欲しいなら、.ToList()を付けるだけ:
var nameList = products.Select(p => p.Name).ToList();
これで普通のリストとして扱えるよ。
要素のインデックスを使う
今どの要素を処理してるか知りたい時もあるよね。Selectにはオーバーロードがあるよ:
var indexed = products.Select((product, index) => new { Index = index, Name = product.Name });
foreach (var item in indexed)
{
Console.WriteLine($"{item.Index}: {item.Name}");
}
例えば、番号付きリストを作る時に便利!
プログラミングにおけるプロジェクション
Selectの使いこなしは面接でもよく聞かれる。これがないと今どきのアプリは作れないよ:
- DBや外部サービスから必要なデータだけ取り出す。
- アプリのレイヤー間でデータを渡すための整形。
- 画面表示やネット送信用にデータを変換。
- メモリ節約(コレクション全体じゃなくて必要なフィールドだけ持つ)。
大量データを扱う会社は、これをビジネスロジックの中心にしてることも多いよ。
7. よくあるミスや注意点
初心者がハマりがちなのが、LINQの遅延実行(deferred execution)の仕組みをちゃんと理解してないこと。例えば、Selectを呼んでも、実際にコレクションをイテレート(foreachやToList()など)するまでクエリは実行されない。これで「処理のパイプライン」を作れるけど、元データを後から変えた場合、結果も変わっちゃうことがあるから注意。
あと、Selectは元のコレクションを変えない。新しいコレクションを作るだけ。だからproducts.Select(...)の後でproductsに新しいフィールドが増えてる…なんてことはないよ。
nullの扱い:プロジェクションでnullのフィールドにアクセスする可能性があるなら、ちゃんと気をつけて。C# 8以降だと、NullReferenceExceptionの可能性があるとコンパイラが警告してくれるよ。
GO TO FULL VERSION