みなさんこんにちは!ヒロポンです!
集計画面を作ってると「このテーブルから ID と名前だけ欲しい」「画面に出す形に整えて渡したい」、こういう場面ってほぼ毎日来ますよね??
で、そのたびに for で回して詰め替えて……ってやってると、地味に時間が溶ける。俺も昔これを愚直に書いてて、レビューで「それ Select 一発でいけるよ」。顔から火が出た。
今回は C# LINQ Select の使い方を、業務SEが現場で一番使う3パターンに絞る。単純射影・匿名型へ整形・インデックス付き。全部コピペで動くコードで出すんで、急いでる人はコードだけ持って帰ってOKです。
💡 DataTable を GroupBy で分割したり Where で絞り込む 話は別記事 C# DataTable を LINQ でフィルタ・GroupBy・分割する3パターン にまとめてます。今回は Select で「列を取り出す・整形する」 ことだけに絞った話です。
結論: LINQ Select は用途で3つ使い分ける
先に結論。Select は 「何を、どんな形で取り出したいか」で3パターンを使い分けるだけです。迷ったらこの表を見て選べばOK。

Select は、元のデータを1件ずつ受け取って「欲しい形」に作り直すメソッド。SQL でいう SELECT 句 とほぼ同じ役割です。元のコレクションの件数は変えず、返す値の形だけを変える。それだけ。
なぜ Select でつまずくのか
こんな単純なメソッドでなんでつまずくのか。理由は2つ。
1つは、Select が その場では実行されないこと。「遅延評価」って呼ばれるやつで、foreach や ToList() を呼ぶまで中身が走りません。知らないと「あれ、ログが出ない?」ってなる。
もう1つは、射影の途中で null を踏むやつ。e.Department.Name みたいにプロパティを辿ると、途中が null だと NullReferenceException で落ちる。SQL の感覚で書いてると、ここでやられます。
この2つは後半のハマりポイントでちゃんと潰します。まずは動くコードから。
最短対処: コピペで動く3つの Select
まずサンプルデータ。社員リストを使います。Department(部署)は参照型で、未所属だと null になりうる。業務でよくある形にしてます。
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public Department Department { get; set; } // 参照型・null になりうる
}
public class Department
{
public string Name { get; set; }
}
パターン①: 単純射影(1列だけ・型付きで取り出す)
俺が一番使うのがこれ。「ID だけ」「名前だけ」の一覧が欲しい時に、for を回さず1行で取れます。
// List から名前だけ取り出す
var names = employees.Select(e => e.Name).ToList();
// DataTable から型付きで1列取り出す(業務SE頻出)
var ids = dt.AsEnumerable()
.Select(r => r.Field<int>("Id"))
.ToList();
Field<int>("Id") で型付きに取れるんで、(int)row["Id"] みたいなキャストはもう要りません。いい感じに短く書けます。
パターン②: 匿名型へ整形(複数列をまとめて画面/CSV用に)
複数の列を「画面に出す形」「CSV に書き出す形」へまとめたい時。匿名型(new { ... })に詰めます。
var view = employees.Select(e => new
{
e.Id,
名前 = e.Name,
年齢 = e.Age
}).ToList();
プロパティ名は日本語でもいけます(名前 = e.Name)。グリッドの列名や CSV ヘッダーにそのまま使えるんで、整形がいい感じに楽になる。
DataTable からならこう。
var rows = dt.AsEnumerable().Select(r => new
{
Id = r.Field<int>("Id"),
Name = r.Field<string>("Name"),
Dept = r.Field<string>("Dept")
}).ToList();
パターン③: インデックス付き Select(行番号・連番を振る)
意外と知られてないのがこれ。Select には 要素と一緒に 0 始まりのインデックスを受け取れるオーバーロードがあります。
var numbered = employees.Select((e, i) => new
{
No = i + 1, // 1 始まりにしたいので +1
名前 = e.Name
}).ToList();
(e, i) の i が行番号。ん? これ for でカウンタ回さんでええやん、ってなりますよね。CSV の「No.」列とか画面の行番号が、これでいい感じにスッキリ片付く。
ハマりポイント: 知らないと一晩飛ぶやつ
コピペで動くのは確認した。ここからは、知らずに本番へ流すと夜中に呼び出されるやつを2つ。
ハマり①: Select は foreach するまで実行されない(遅延評価)
俺はこれで、仕込んだログが出力されなくて30分くらい無駄にしました。先に1個だけコピペで再現してみます。
class Program
{
static void Main()
{
var names = new[] { "佐藤", "鈴木", "高橋" };
var query = names.Select(n =>
{
Console.WriteLine("射影中: " + n);
return n + " さん";
});
Console.WriteLine("--- foreach 前 ---");
foreach (var x in query) { }
Console.WriteLine("--- foreach 後 ---");
}
}
実行するとこうなります。
--- foreach 前 ---
射影中: 佐藤
射影中: 鈴木
射影中: 高橋
--- foreach 後 ---

Select を書いた行では 射影中: が1つも出てない。foreach で初めて走ってるのが分かりますよね??「ログを仕込んだのに出ない」時は、たいてい「まだ評価されてない」が犯人です。ToList() で即時に確定させると安全。
ハマり②: 射影の途中で null を踏んで NullReferenceException
これはテスト中にヒヤッとしたやつ。参照型プロパティを辿る射影は、途中の null で落ちます。
class Dept { public string Name { get; set; } }
class Emp { public string Name { get; set; } public Dept Dept { get; set; } }
class Program
{
static void Main()
{
var emps = new[]
{
new Emp { Name = "佐藤", Dept = new Dept { Name = "経理" } },
new Emp { Name = "鈴木", Dept = null } // 部署 未設定
};
// Dept が null の行で落ちる
var ng = emps.Select(e => e.Dept.Name).ToList(); // NullReferenceException
Console.WriteLine(string.Join(", ", ng));
}
}
エラー再現:

2件目の「鈴木」で e.Dept.Name を辿った瞬間に NullReferenceException。対処は null 条件演算子(?.)と null 合体演算子(??)で逃がすだけ。
var ok = emps.Select(e => e.Dept?.Name ?? "(未所属)").ToList();
// → "経理", "(未所属)"
開発機のテストデータは全部きれいに埋まってる。本番の「部署 未設定」データで初めて踏む、これが業務系のあるあるパターンなんですよね。射影でプロパティを辿る時は、null が来る前提で ?. を癖にしておく。これだけで事故が減ります。
現場メモ: 一覧作成で時間を溶かさないために
流通系の基幹システムを保守してた頃の話。夜間バッチの出力 CSV を「画面でも確認したい」って要望が来たことがあって。数万件の取引履歴を、ID・取引日・金額の3列だけ匿名型に整形して、画面のグリッドに流すやつを Select 一発でやりました。
最初は愚直に DataRow を1件ずつ手で詰め替えてたんですよ。コードが縦に長くなるし、列が1つ増えるたびに直す箇所が3か所くらいある。Select(r => new { ... }) に置き換えたら、詰め替えループが丸ごと消えて、列追加も1行で済む。
ポイントは、画面に出す直前で ToList() して確定させること。遅延評価のまま画面のバインドに渡すと、スクロールのたびに再評価が走って「なんか重い?」になります。整形した結果は早めに確定。これだけで体感の重さが変わる。
まとめ
LINQ Select の3パターン、整理するとこう。
- ① 単純射影 — 1列だけ欲しい時。
Select(e => e.Name) - ② 匿名型へ整形 — 複数列を画面/CSV用にまとめたい時。
Select(e => new { ... }) - ③ インデックス付き — 連番・行番号を振りたい時。
Select((e, i) => ...)
ハマりは「遅延評価」と「null 射影」の2つ。ToList() で確定、?. で null 回避。この2つを押さえとけば、一覧作成で詰まることはほぼ無くなります!!
よくある質問
Q1. Select と Where の違いは何ですか?
A. Where は 行を絞り込む(件数が減る)、Select は 列を作り直す(件数は変わらない)です。SQL でいう WHERE句 と SELECT句 の関係と同じ。両方使う時は Where(...).Select(...) の順で繋ぎます。
Q2. 匿名型で返した結果を、メソッドの戻り値にできますか?
A. 匿名型はそのメソッドの中でしか型名を書けないので、return で外に渡すのは基本できません。外に渡したいなら、ちゃんとしたクラス(DTO)か record を定義して Select(e => new 社員ViewDto { ... }) にするのが定石です。
Q3. Select((e, i) => …) の i は SQL の ROW_NUMBER() と同じですか?
A. 似てますが別物です。i は メモリ上のコレクションの並び順で 0 始まりに振られるだけ。DB 側で並べ替えた順番が欲しいなら、SQL で ORDER BY してから取得するか、ROW_NUMBER() を使ってください。
Q4. ToList() と ToArray()、どっちを使えばいい?
A. 後から件数が変わる・追加削除するなら ToList()、件数が固定で読むだけなら ToArray() がわずかに軽いです。業務だと迷ったら ToList() でOK。困ることはほぼ無いです。
Q5. Select の中で DB アクセスなど重い処理を書いていい?
A. やめたほうがいいです。遅延評価のせいで、foreach や画面バインドのたびに何回も走る危険がある。重い処理は Select の外で済ませるか、ToList() で1回だけに確定させてからにしてください。
ここまでで LINQ Select の使い分けは押さえられたはず。
ただ、こういう「知ってれば3秒、知らないと30分」の小技って、現場で一個ずつ拾っていくしかないんですよね。で、この地味な積み重ねを続けてる業務SEほど、なぜか自分の市場価値を低く見積もりがち。「俺なんてただの保守要員だから」って。その辺の自己評価の話を、下の関連記事の最後に置いときます。技術の合間の箸休めにどうぞ。
関連記事
- C# DataTable を LINQ でフィルタ・GroupBy・分割する3パターン — 今回の Select と合わせて使う「絞り込み・分割」側の話。一緒に読むと DataTable 操作がほぼ網羅できます
- EF6 + LINQ で N+1 問題を踏まない3つの書き方 — Select で関連データを引く時にやりがちな「N+1」を踏まないための応用編
- C# Linq で Null を回避する書き方とパフォーマンス — 今回のハマり②で触れた null 射影を、もっと深く潰したい人向け
- C# DataTable の大量行を Skip&Take でページングする3パターン — 数万件を Select で整形する前に「1000件ずつ」に区切りたくなった時の設計
- 業務SE が市場価値を語る時に避けるべき3つの自己否定 — 口癖が「俺なんてただの業務SEだから」になってる人へ。技術記事の箸休めにどうぞ
以上!
同じところで詰まってる人いたら、どんどんシェア待ってるぜ!!
執筆者
バイブス父さん — 業務 SE 7 年 (正社員 2 / フリーランス 5)。 現職は SEO 直轄部の AI アドバイザー兼 PL、 副業で中小 SIer の CTO。 SES 複数社・フリーランスエージェント複数経由の経験ベースで「業務 SE 視点」 の技術 + キャリア記事を書いています。
🐦 X: @hiro_progra0524 (日々の現場メモ更新中)
📝 About Me で経歴詳細を見る


コメント