2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C# LINQ サンプル集|Where/Select/GroupBy/Join(実行結果つき)【鍛錬K35】

2
Last updated at Posted at 2026-02-09

連載Index(読む順・公開済リンクが最新): S00_門前の誓い_総合Index
遅くなる書き方のチェック表: LINQが遅くなる書き方5選 (to Objects)

LINQは便利ですが、WhereSelect までは何となく書けても、JoinGroupJoin、遅延実行あたりで急に見えにくくなります。
自分で書く場面だけでなく、人が書いたメソッドチェーンを読む場面でも止まりやすいです。

このページは、実行例と出力を並べて確認できる LINQ to Objects のサンプル集です。
「まず動く形を見たい」「人のLINQが読めるようになりたい」「後で見返せる一覧がほしい」という場面向けにまとめています。

  • 対象: C# / .NET でLINQを書く場面がある人、読むたびに止まりやすい人
  • 前提: List<T> / 配列に対するLINQ(LINQ to Objects)
  • 完成コード: GitHubのサンプル一式はこちら

このページで見えること

  • Where / Select / OrderBy など基本操作の動き
  • Join / GroupJoin / ToDictionary など実務で出やすい処理
  • 遅延実行と ToList() の違い
  • 実行結果つきの短いサンプル

補足

  • このページの例は List<T> / 配列など、メモリ上のコレクションに対するLINQ(LINQ to Objects)を前提にしている
  • 多くのLINQは遅延実行。列挙された瞬間に処理が動き始める(「遅延実行の確認」で短く見る)
  • ToList() / ToArray() は実体化。ここで列挙が走って結果が確定し、以降はその確定結果を使う
  • Chunk は .NET 6 以降。.NET Framework などの場合は代替も併記してある
  • 遅くなる書き方のチェック表: LINQ(to Objects)が遅くなる書き方5選

ありがちな詰まり

状況 起きがちなこと
チェーンが短く書ける 途中の値が見えず、最後の結果だけ見て迷いやすい
何となく動く 意図とズレた瞬間に、どこを直すか見えにくい
手を入れたい 変更開始点が決まらず、試す回数が増えやすい

このページの見方

  • 先に「使用データ構造」を置く
  • そのデータに対してLINQを適用する
  • 各項目はこの順で並べる
  1. 関数名
  2. 説明
  3. 使いやすい場面
  4. 実行例
  5. 出力(結果)

使用データ構造

表の見出しは表示名(日本語)。次の1行はDBカラム想定のキー(英語)。

Departments

部門ID 部門名
Id Name
1 Platform
2 Sales
3 QA

Employees

社員ID 氏名 部門ID 年齢 給与 スキル 入社日 在籍中
Id Name DeptId Age Salary Skills Joined IsActive
1 Alice 1 34 800 C#, LINQ, SQL 2018-04-01 true
2 Bob 1 28 650 C#, WPF 2021-10-15 true
3 Carol 2 41 900 Excel, Negotiation 2015-01-20 true
4 Dave 3 25 550 C#, xUnit, Selenium 2023-07-01 true
5 Erin 3 37 750 Python, Playwright 2019-11-05 false
6 Frank 2 30 620 SQL, PowerBI 2022-02-10 true

Orders

受注ID 社員ID 金額 受注日
Id EmployeeId Amount OrderedAt
1 1 12000 2025-01-10
2 1 8000 2025-02-05
3 3 30000 2025-01-12
4 6 15000 2025-03-03
5 2 5000 2025-02-20
6 4 7000 2025-03-15

準備コード

J(...) は「結果を1行で表示する」ための補助関数です。
配列や列挙結果を ", " 区切りでまとめて出す用途に使います。
複数行で出したいところは foreach でそのまま出します。

using System;
using System.Collections.Generic;
using System.Linq;

// 部門マスタ用
public sealed class Department
{
    public int Id { get; }
    public string Name { get; }

    public Department(int id, string name) { Id = id; Name = name; }
}

// 社員データ用
public sealed class Employee
{
    public int Id { get; }
    public string Name { get; }
    public int DeptId { get; }
    public int Age { get; }
    public int Salary { get; }
    public string[] Skills { get; }
    public DateTime Joined { get; }
    public bool IsActive { get; }

    public Employee(int id, string name, int deptId, int age, int salary, string[] skills, DateTime joined, bool isActive)
    {
        Id = id; Name = name; DeptId = deptId; Age = age; Salary = salary; Skills = skills; Joined = joined; IsActive = isActive;
    }
}

// 受注データ用
public sealed class Order
{
    public int Id { get; }
    public int EmployeeId { get; }
    public int Amount { get; }
    public DateTime OrderedAt { get; }

    public Order(int id, int employeeId, int amount, DateTime orderedAt)
    {
        Id = id; EmployeeId = employeeId; Amount = amount; OrderedAt = orderedAt;
    }
}

// サンプル全体で使う固定データ
public static class DemoData
{
    public static readonly List<Department> Departments = new List<Department>()
    {
        new Department(1, "Platform"),
        new Department(2, "Sales"),
        new Department(3, "QA"),
    };

    public static readonly List<Employee> Employees = new List<Employee>()
    {
        new Employee(1, "Alice", 1, 34, 800, new[] { "C#", "LINQ", "SQL" },        new DateTime(2018, 4, 1),  true),
        new Employee(2, "Bob",   1, 28, 650, new[] { "C#", "WPF" },               new DateTime(2021,10,15),  true),
        new Employee(3, "Carol", 2, 41, 900, new[] { "Excel", "Negotiation" },    new DateTime(2015, 1,20),  true),
        new Employee(4, "Dave",  3, 25, 550, new[] { "C#", "xUnit", "Selenium" }, new DateTime(2023, 7, 1),  true),
        new Employee(5, "Erin",  3, 37, 750, new[] { "Python", "Playwright" },    new DateTime(2019,11, 5),  false),
        new Employee(6, "Frank", 2, 30, 620, new[] { "SQL", "PowerBI" },          new DateTime(2022, 2,10),  true),
    };

    public static readonly List<Order> Orders = new List<Order>()
    {
        new Order(1, 1, 12000, new DateTime(2025, 1,10)),
        new Order(2, 1,  8000, new DateTime(2025, 2, 5)),
        new Order(3, 3, 30000, new DateTime(2025, 1,12)),
        new Order(4, 6, 15000, new DateTime(2025, 3, 3)),
        new Order(5, 2,  5000, new DateTime(2025, 2,20)),
        new Order(6, 4,  7000, new DateTime(2025, 3,15)),
    };
}

// 列挙結果を1行で見やすく表示
static string J<T>(IEnumerable<T> xs) => string.Join(", ", xs);

遅延実行の確認

LINQは「書いた行」では動かず、「列挙した行」で動くことが多いです。
ここは一度だけ、Console出力で見える形にしています。

例:列挙された瞬間に処理が動き始める

  • 説明: q を作るだけでは何も出ない。foreach で列挙した瞬間に Select が動き始める
  • 使いやすい場面:
    • 遅延実行のつまずきどころを先に見ておきたい
    • WhereSelect の中に重い処理を入れてよいか判断したい
    • 同じクエリを何回も回していないか点検したい
  • 実行例:
var q = DemoData.Employees
    .Where(e => e.IsActive)
    .Select(e =>
    {
        // ここはクエリ作成時ではなく、列挙時に実行
        Console.WriteLine("hit: " + e.Name);
        return e.Name;
    });

Console.WriteLine("created");

// ここで初めて列挙開始
foreach (var _ in q) { }

Console.WriteLine("done");
  • 結果:
created
hit: Alice
hit: Bob
hit: Carol
hit: Dave
hit: Frank
done

例:同じクエリを2回列挙すると2回動く

  • 説明: 遅延列は列挙のたびに同じ処理が動く。重い処理が入ると、その分だけ繰り返し走る
  • 使いやすい場面:
    • 一覧表示前に不要な再列挙がないか見たい
    • Count()foreach を別々に呼んでいないか見たい
    • ToList() の置き場所を考えたい
  • 実行例:
var q = DemoData.Employees
    .Where(e => e.IsActive)
    .Select(e =>
    {
        // 列挙のたびにここが再実行
        Console.WriteLine("hit: " + e.Name);
        return e.Name;
    });

Console.WriteLine("-- first");
foreach (var _ in q) { }

Console.WriteLine("-- second");
foreach (var _ in q) { }
  • 結果:
-- first
hit: Alice
hit: Bob
hit: Carol
hit: Dave
hit: Frank
-- second
hit: Alice
hit: Bob
hit: Carol
hit: Dave
hit: Frank

実体化すると、列挙は1回で止まる

  • 説明: ToList() で実体化すると、そこで列挙が終わる。以降は List を回すだけになる
  • 使いやすい場面:
    • 同じ結果を画面表示とCSV出力で共用したい
    • 列挙コストを1回にしたい
    • 後続処理で結果を安定させたい
  • 実行例:
var q = DemoData.Employees
    .Where(e => e.IsActive)
    .Select(e =>
    {
        // ToList() の時点でここまで実行
        Console.WriteLine("hit: " + e.Name);
        return e.Name;
    });

Console.WriteLine("-- materialize");
var list = q.ToList();

Console.WriteLine("-- enumerate 1");
foreach (var _ in list) { }

Console.WriteLine("-- enumerate 2");
foreach (var _ in list) { }
  • 結果:
-- materialize
hit: Alice
hit: Bob
hit: Carol
hit: Dave
hit: Frank
-- enumerate 1
-- enumerate 2

遅延実行が助かる場面

  • Any() / FirstOrDefault() など、見つかった時点で止まる処理で無駄に回さない
  • Take(n) と組み合わせて「先頭だけ見る」を軽くやる
  • 条件を組み立てて、最後に1回だけ列挙する(途中で何度も回さない)

絞り込み

Where

  • 説明: SQLの WHERE と同じ。条件に一致する要素だけ残す
  • 使いやすい場面:
    • 在籍中の社員だけ画面に出したい
    • 削除済みや無効データを除いて集計したい
    • 条件に合う候補だけ次の SelectOrderBy に流したい
  • 実行例:
var activeNames = DemoData.Employees
    .Where(e => e.IsActive)
    .Select(e => e.Name);

Console.WriteLine(J(activeNames));
  • 結果:
Alice, Bob, Carol, Dave, Frank

Take

  • 説明: 先頭から指定数だけ取る。SQLの TOP / LIMIT の感覚に近い
    先頭の意味を決めたい場合は、先に OrderBy で並びを作ってから使う
  • 使いやすい場面:
    • 先頭3件だけ確認したい
    • ダッシュボードで上位N件だけ出したい
    • テスト時にデータを少量だけ流したい
  • 実行例:
var earliest3 = DemoData.Employees
    .OrderBy(e => e.Joined)
    .Take(3)
    .Select(e => e.Name);

Console.WriteLine(J(earliest3));
  • 結果:
Carol, Alice, Erin

Skip

  • 説明: 先頭から指定数だけ飛ばす。ページングでよく使う
    ここも先頭の意味を決めたい場合は OrderBy を先に入れる
  • 使いやすい場面:
    • 2ページ目以降のデータを取りたい
    • 先頭のヘッダ相当データを読み飛ばしたい
    • テストで途中からだけ確認したい
  • 実行例:
var rest = DemoData.Employees
    .OrderBy(e => e.Joined)
    .Skip(3)
    .Select(e => e.Name);

Console.WriteLine(J(rest));
  • 結果:
Bob, Frank, Dave

TakeWhile

  • 説明: 先頭から順に見て、条件に一致している間だけ取り続ける。
    最初に条件に一致しない要素が出たら、その時点で打ち切りになり、残りは見ない。
    途中に一致しない要素が入った場合、そこで終わる。
  • 使いやすい場面:
    • 時系列の履歴で「エラーが出る手前まで」だけ集めたい
    • 先頭から境界が1回だけ出るデータを切り出したい
    • 並び順が意味を持つデータを先頭から処理したい
  • 実行例:
var under35 = DemoData.Employees
    .OrderBy(e => e.Age)
    // 条件に合う間だけ取り続ける
    .TakeWhile(e => e.Age < 35)
    .Select(e => $"{e.Name}({e.Age})");

Console.WriteLine(J(under35));
  • 結果:
Dave(25), Bob(28), Frank(30), Alice(34)

SkipWhile

  • 説明: 先頭から順に見て、条件に一致している間は捨て続ける
    最初に条件に一致しない要素が出たら、そこから先は全部残す
    「先頭の前置きだけ飛ばして、本編から取りたい」用途に向く。
  • 使いやすい場面:
    • ログ先頭のヘッダや初期化メッセージだけ飛ばしたい
    • 締め日より前を飛ばして以降だけ見たい
    • 先頭の連続した不要部分だけ除きたい
  • 実行例:
var from35 = DemoData.Employees
    .OrderBy(e => e.Age)
    // 条件に合う間だけ飛ばす
    .SkipWhile(e => e.Age < 35)
    .Select(e => $"{e.Name}({e.Age})");

Console.WriteLine(J(from35));
  • 結果:
Erin(37), Carol(41)

変換

Select

  • 説明: SQLの SELECT と同じ感覚で「列を取り出す」「計算列を作る」。
    行を減らすのは Where の役目で、Select は基本、件数を変えない。
    例:Employee から Name だけ抜く、表示用に Name(Age) を作る
  • 使いやすい場面:
    • 一覧表示に必要な列だけ抜きたい
    • DTOや匿名型へ詰め替えたい
    • 表示用の文字列をその場で作りたい
  • 実行例:
var names = DemoData.Employees.Select(e => e.Name);
Console.WriteLine(J(names));
  • 結果:
Alice, Bob, Carol, Dave, Erin, Frank

SelectMany

  • 説明: 1件の中に「配列や一覧」が入っている形を、行として展開して1本にする。
    SQLで言うと、子の一覧を行に展開して結果行が増えるイメージに近い。
    その上で Distinct() を入れると、SQLの SELECT DISTINCT の感覚になる
  • 使いやすい場面:
    • 社員ごとのスキル一覧を全体一覧へ展開したい
    • 親子構造の子だけを横断で集めたい
    • タグや権限の重複なし一覧を作りたい
  • 実行例:
var allSkills = DemoData.Employees
    .SelectMany(e => e.Skills)
    .Distinct()
    .OrderBy(s => s);

foreach (var s in allSkills)
{
    Console.WriteLine(s);
}
  • 結果:
C#
Excel
LINQ
Negotiation
Playwright
PowerBI
Python
SQL
Selenium
WPF
xUnit

並べ替え

OrderBy

  • 説明: SQLの ORDER BY と同じ。キーで昇順に並べる
  • 使いやすい場面:
    • 入社日順や名前順で一覧表示したい
    • TakeFirstOrDefault の前に基準順を決めたい
    • テスト結果の並びを安定させたい
  • 実行例:
var byDept = DemoData.Employees
    .OrderBy(e => e.DeptId)
    .Select(e => $"{e.DeptId}:{e.Name}");

Console.WriteLine(J(byDept));
  • 結果:
1:Alice, 1:Bob, 2:Carol, 2:Frank, 3:Dave, 3:Erin

ThenBy

  • 説明: SQLの ORDER BY DeptId, Salary DESC の感覚。2つ目以降のキーで追加の並べ替えを入れる
  • 使いやすい場面:
    • 部門順の中で給与順にしたい
    • 名前順の中で日付順にしたい
    • 一覧の主キー順に加えて副条件も入れたい
  • 実行例:
var byDeptThenSalary = DemoData.Employees
    .OrderBy(e => e.DeptId)
    .ThenByDescending(e => e.Salary)
    .Select(e => $"{e.DeptId}:{e.Name}({e.Salary})");

Console.WriteLine(J(byDeptThenSalary));
  • 結果:
1:Alice(800), 1:Bob(650), 2:Carol(900), 2:Frank(620), 3:Erin(750), 3:Dave(550)

集合演算

Distinct

  • 説明: SQLの DISTINCT に近い。重複を除く
  • 使いやすい場面:
    • 部門IDの重複なし一覧がほしい
    • タグや権限の一覧を重複なしで出したい
    • フィルタ候補の一覧を作りたい
  • 実行例:
var deptIds = DemoData.Employees.Select(e => e.DeptId).Distinct();
Console.WriteLine(J(deptIds));
  • 結果:
1, 2, 3

Union

  • 説明: 2つの集合を足し合わせ、重複を除く(SQLの UNION 相当)
  • 使いやすい場面:
    • 2人の保有スキルをまとめて見たい
    • 複数条件の候補を重複なしで集めたい
    • マスタ候補を複数ソースからまとめたい
  • 実行例:
var alice = DemoData.Employees.First(e => e.Name == "Alice").Skills;
var dave  = DemoData.Employees.First(e => e.Name == "Dave").Skills;

var union = alice.Union(dave).OrderBy(s => s);
Console.WriteLine(J(union));
  • 結果:
C#, LINQ, SQL, Selenium, xUnit

Intersect

  • 説明: 共通部分を取る(SQLの INTERSECT 相当)
  • 使いやすい場面:
    • 2人に共通するスキルを見たい
    • 2つの権限集合の重なりを調べたい
    • 検索条件どうしの共通候補を取りたい
  • 実行例:
var alice = DemoData.Employees.First(e => e.Name == "Alice").Skills;
var dave  = DemoData.Employees.First(e => e.Name == "Dave").Skills;

var common = alice.Intersect(dave);
Console.WriteLine(J(common));
  • 結果:
C#

Except

  • 説明: 左から右を引く(SQLの EXCEPT 相当)
  • 使いやすい場面:
    • ある人だけが持つスキルを見たい
    • 対象外IDを除いた一覧を作りたい
    • 「全候補 - 処理済み」の差分を取りたい
  • 実行例:
var alice = DemoData.Employees.First(e => e.Name == "Alice").Skills;
var dave  = DemoData.Employees.First(e => e.Name == "Dave").Skills;

var onlyAlice = alice.Except(dave);
Console.WriteLine(J(onlyAlice));
  • 結果:
LINQ, SQL

量の確認

Any

  • 説明: 条件に一致する要素が1つでもあるか。SQLの EXISTS の感覚に近い
  • 使いやすい場面:
    • 対象データが1件でもあるか先に見たい
    • 警告表示の有無を判定したい
    • Count() > 0 の代わりに存在確認をしたい
  • 実行例:
var hasInactive = DemoData.Employees.Any(e => !e.IsActive);
Console.WriteLine(hasInactive);
  • 結果:
True

All

  • 説明: 全要素が条件に一致するか。否定条件の方が読みやすい場面も多い
  • 使いやすい場面:
    • 全件が有効か確認したい
    • バリデーション通過済みか見たい
    • 一括処理前の前提確認をしたい
  • 実行例:
var allActive = DemoData.Employees.All(e => e.IsActive);
Console.WriteLine(allActive);
  • 結果:
False

集計

Count

  • 説明: 件数を数える(SQLの COUNT
  • 使いやすい場面:
    • 対象件数を表示したい
    • 条件一致数を確認したい
    • 上限件数チェックをしたい
  • 実行例:
var activeCount = DemoData.Employees.Count(e => e.IsActive);
Console.WriteLine(activeCount);
  • 結果:
5

Sum

  • 説明: 合計を取る(SQLの SUM
  • 使いやすい場面:
    • 売上や金額の合計を出したい
    • 数量合計を見たい
    • 明細の小計を出したい
  • 実行例:
var sumSalary = DemoData.Employees.Sum(e => e.Salary);
Console.WriteLine(sumSalary);
  • 結果:
4270

Average

  • 説明: 平均を取る(SQLの AVG
  • 使いやすい場面:
    • 平均給与や平均点を見たい
    • 期間平均を見たい
    • 大きな偏りがないか軽く見たい
  • 実行例:
var avgSalary = DemoData.Employees.Average(e => e.Salary);
Console.WriteLine(avgSalary);
  • 結果:
711.6666666666666

Min

  • 説明: 最小値を取る(SQLの MIN
  • 使いやすい場面:
    • 最小金額や最古日付を見たい
    • 下限値を確認したい
    • 範囲チェックの基準を見たい
  • 実行例:
var minSalary = DemoData.Employees.Min(e => e.Salary);
Console.WriteLine(minSalary);
  • 結果:
550

Max

  • 説明: 最大値を取る(SQLの MAX
  • 使いやすい場面:
    • 最大金額や最新日付を見たい
    • 上限値を確認したい
    • ピーク値を見たい
  • 実行例:
var maxSalary = DemoData.Employees.Max(e => e.Salary);
Console.WriteLine(maxSalary);
  • 結果:
900

Aggregate

  • 説明: 先頭から順に、値を合成して1つにまとめる。
    acc(途中結果)に次の要素を足していく形で、まとめ方を自分で決められる。
  • 使いやすい場面:
    • ログ表示用に "A -> B -> C" のような連結文字列を作りたい
    • string.Join / Sum では表現しづらい独自ルールの合成をしたい
    • 1つの結果へ畳み込みたい
  • 実行例:
var chain = DemoData.Employees
    .Select(e => e.Name)
    .Aggregate((acc, x) => acc + " -> " + x);

Console.WriteLine(chain);
  • 結果:
Alice -> Bob -> Carol -> Dave -> Erin -> Frank

要素取得

FirstOrDefault

  • 説明: 先頭要素を取る。空なら null になる(string など)
    先頭の意味を決めたい場合は、先に OrderBy を入れてから使う
  • 使いやすい場面:
    • 条件に合う最初の1件を取りたい
    • 並び順を決めた上で代表1件を取りたい
    • 空の可能性を許容しつつ軽く取りたい
  • 実行例:
var firstQa = DemoData.Employees
    .Where(e => e.DeptId == 3)
    .OrderBy(e => e.Joined)
    .Select(e => e.Name)
    .FirstOrDefault();

Console.WriteLine(firstQa ?? "(空)");
  • 結果:
Erin

SingleOrDefault

  • 説明: 条件に一致する要素が「0件 or 1件」だと決めて取り出す。
    0件なら null。2件以上あると例外になる。
    つまり「1件のはず」をコードで表に出して、ズレたらその場で気づけるようにする。
    FirstOrDefault は「複数あっても先頭を取る」。SingleOrDefault は「複数ある時点で前提ずれ」を見つけたい場面向き
  • 使いやすい場面:
    • IDなど「重複しないはず」の条件で1件を取りたい
    • 設定値やマスタの読み出しで一意を前提にしたい
    • データ異常を早めに見つけたい
  • 実行例:
var bob = DemoData.Employees.SingleOrDefault(e => e.Id == 2);
var none = DemoData.Employees.SingleOrDefault(e => e.Id == 999);

Console.WriteLine(bob?.Name ?? "(null)");
Console.WriteLine(none?.Name ?? "(null)");
  • 結果:
Bob
(null)

ElementAt

  • 説明: 指定インデックスの要素を取る
    並べ替え後の「2番目」「3番目」を取りたい場面で使う
  • 使いやすい場面:
    • 上位2番目を見たい
    • 特定順位の要素を取りたい
    • ページ内の相対位置から1件を取りたい
  • 実行例:
var secondHighest = DemoData.Employees
    .OrderByDescending(e => e.Salary)
    .Select(e => e.Name)
    .ElementAt(1);

Console.WriteLine(secondHighest);
  • 結果:
Alice

DefaultIfEmpty

  • 説明: 空なら1件だけ既定値を流す
    「空のときも1行だけ出したい」「その後の処理を止めたくない」場面で使われる
  • 使いやすい場面:
    • 検索結果が空でもメッセージ1件を出したい
    • 集計や表示の流れを止めたくない
    • 外部結合相当の考え方を見たい
  • 実行例:
var dept99Names = DemoData.Employees
    .Where(e => e.DeptId == 99)
    .Select(e => e.Name)
    .DefaultIfEmpty("(なし)");

Console.WriteLine(J(dept99Names));
  • 結果:
(なし)

結合

Join

  • 説明: SQLの INNER JOIN に近い。キーを突き合わせて結合する
  • 使いやすい場面:
    • 社員へ部門名を付けて表示したい
    • 明細へマスタ名を付けたい
    • IDだけの列を表示用の情報へ変えたい
  • 実行例:
var joined = DemoData.Employees.Join(
    DemoData.Departments,
    e => e.DeptId,
    d => d.Id,
    (e, d) => $"{e.Name}-{d.Name}");

Console.WriteLine(J(joined));
  • 結果:
Alice-Platform, Bob-Platform, Carol-Sales, Dave-QA, Erin-QA, Frank-Sales

GroupJoin

  • 説明: 親に子の一覧をぶら下げる形で結合する
    親を基準にして、子をまとめて扱いたい場面で使う
  • 使いやすい場面:
    • 部門ごとの社員一覧を画面に出したい
    • 親明細ごとに子明細を束ねて扱いたい
    • 一覧画面や帳票用に親単位でまとめたい
  • 実行例:
var grouped = DemoData.Departments.GroupJoin(
    DemoData.Employees,
    d => d.Id,
    e => e.DeptId,
    // 親1件に対して子の列がぶら下がる形
    (d, es) => $"{d.Name}: {J(es.Select(x => x.Name))}");

foreach (var line in grouped)
{
    Console.WriteLine(line);
}
  • 結果:
Platform: Alice, Bob
Sales: Carol, Frank
QA: Dave, Erin

変換と生成

ToList

  • 説明: 遅延列を List として実体化する。この呼び出しで列挙が走り、結果が List に確定する。
    同じ結果を何度も使う場面だと、ToList() で一度確定しておく方が見通しを立てやすい
  • 使いやすい場面:
    • 同じ結果を何回か使いたい
    • 再列挙を避けたい
    • デバッグ中に確定結果を見たい
  • 実行例:
var list = DemoData.Employees
    .Where(e => e.IsActive)
    .Select(e => e.Name)
    .ToList();

Console.WriteLine(list.GetType().Name);
Console.WriteLine(J(list));
  • 結果:
List`1
Alice, Bob, Carol, Dave, Frank

ToDictionary

  • 説明: 列を Dictionary(連想配列) に変換する。
    「キー → 値」で素早く引ける形にしたいときに使う。
    例:社員一覧から 社員ID → 氏名 の辞書を作って、あとで dict[id] で名前を取り出す。
    ※ 同じキーが2回出ると例外になる
    ※ キー重複の可能性があるデータでは ToLookup も候補になる
  • 使いやすい場面:
    • 社員IDから氏名をすぐ引きたい
    • 部門IDから部門名を引くマスタを作りたい
    • ループ中に毎回 FirstOrDefault せず、先に検索表を作って使いたい
  • 実行例:
var dict = DemoData.Employees.ToDictionary(e => e.Id, e => e.Name);
Console.WriteLine(dict[3]);
  • 結果:
Carol

ToLookup

  • 説明: 1キーに複数の値をぶら下げる検索表にする
    ToDictionary と違い、同じキーが複数あっても自然に受ける
  • 使いやすい場面:
    • 部門IDごとに社員名一覧を引きたい
    • 1対多のまとまりを高速に見たい
    • キー重複ありのデータをそのまま扱いたい
  • 実行例:
var lookup = DemoData.Employees.ToLookup(e => e.DeptId, e => e.Name);
Console.WriteLine(J(lookup[3]));
  • 結果:
Dave, Erin

Zip

  • 説明: 2つの列を 同じ位置どうしでペア にして、1列にまとめる。
    zip は「圧縮ファイルのZIP」と同じ単語で、あれのイメージに近い。
    • 圧縮のZIP:複数ファイルを 1つにまとめて束ねる
    • LINQのZip:2つの列を 1本に束ねる(同じインデックスを1組にする)
      使いどころは「対応が決まっている2列」を合体したい場面。
      例:名前の列給与の列 を合わせて "Alice(800)" みたいな表示用にする、など。
  • 使いやすい場面:
    • 画面表示やログで「ラベル列」と「値列」を ラベル: 値 にしたい
    • CSVなどで別列で持っている ID列名称列 を合成して表示用にしたい
    • 2つの配列を同じ長さで持っていて、1つのDTOや匿名型へまとめたい
  • 実行例:
var nameAndSalary = DemoData.Employees
    .Select(e => e.Name)
    .Zip(DemoData.Employees.Select(e => e.Salary),
        // 同じ位置どうしを1組にする
        (name, salary) => $"{name}({salary})");

Console.WriteLine(J(nameAndSalary));
  • 結果:
Alice(800), Bob(650), Carol(900), Dave(550), Erin(750), Frank(620)

Chunk

  • 説明: 指定サイズで区切る(.NET 6 以降)
  • 使いやすい場面:
    • 一覧を2件ずつ画面表示したい
    • バッチ送信を一定件数ごとに分けたい
    • 印刷やレイアウト用に小分けしたい
  • 実行例:
var chunks = DemoData.Employees
    .Select(e => e.Name)
    // 2件ずつの配列へ分割
    .Chunk(2)
    .Select(block => $"[{J(block)}]");

Console.WriteLine(J(chunks));
  • 結果:
[Alice, Bob], [Carol, Dave], [Erin, Frank]
  • 代替(.NET Framework など):
var names = DemoData.Employees.Select(e => e.Name).ToList();

var blocks = new List<List<string>>();
for (int i = 0; i < names.Count; i += 2)
{
    blocks.Add(names.Skip(i).Take(2).ToList());
}

Console.WriteLine(J(blocks.Select(b => $"[{J(b)}]")));

Append

  • 説明: 末尾に1要素を付け足す
  • 使いやすい場面:
    • 一覧の最後に「その他」を足したい
    • テスト用のダミー1件を末尾へ足したい
    • UI表示前に補助行を加えたい
  • 実行例:
var xs = DemoData.Employees.Select(e => e.Name).Append("Zoe");
Console.WriteLine(J(xs));
  • 結果:
Alice, Bob, Carol, Dave, Erin, Frank, Zoe

Prepend

  • 説明: 先頭に1要素を付け足す
  • 使いやすい場面:
    • 一覧の先頭に「選択してください」相当の行を足したい
    • ログや表示の先頭へ見出し相当の値を足したい
    • テスト時に先頭ケースを加えたい
  • 実行例:
var xs = DemoData.Employees.Select(e => e.Name).Prepend("Start");
Console.WriteLine(J(xs));
  • 結果:
Start, Alice, Bob, Carol, Dave, Erin, Frank

Concat

  • 説明: 2つの列をつなげる(重複は残る)。SQLの UNION ALL の感覚に近い
  • 使いやすい場面:
    • 2つの一覧をそのまま前後に連結したい
    • 既存データへ追加候補を後ろへ並べたい
    • 重複を残したまま順番つきでつなぎたい
  • 実行例:
var xs = new[] { "A", "B" }.Concat(new[] { "B", "C" });
Console.WriteLine(J(xs));
  • 結果:
A, B, B, C

LINQは短く書ける分、途中の値や列挙タイミングが見えにくくなりやすいです。
このページでは、まず動く形と結果を先に見て、その後に用途を思い出せるように並べています。
実務で「あの書き方どうだったか」をすぐ引きたい場面の早見表として使える形を狙っています。

完成コードはGitHubに置いてあります。
動かしながら見たい場合は、こちらからどうぞ。
GitHubのサンプル一式はこちら

連載Index(読む順・公開済リンクが最新): S00_門前の誓い_総合Index
遅くなる書き方のチェック表: LINQが遅くなる書き方5選 (to Objects)

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?