Skip to content

第12章 List<T> と LINQ

この章では、複数のデータをまとめて扱う List<T> と、データの検索・並び替え・集計を短く書く LINQ を学習します。

この章は 2 部構成 です。

List<T> は配列の進化版で、要素の追加・削除が自由にできる「データの入れ物」です。 第 6 章の配列には「作成時に要素数が決まる」「後から追加・削除しにくい」という制約がありましたが、List<T> はそれを解消します。

List<int> scores = new List<int>();
scores.Add(80);
scores.Add(75);

そして、List<T>foreach と組み合わせるだけで、一覧表示・検索・集計(合計・人数・平均)まで、実用的な処理はひととおりこなせます。 LINQ を知らなくても、List<T> は単体で十分に使えます。 まずはこの前半を確実にしましょう。

後半:LINQ とラムダ式でもっと便利に(発展)

Section titled “後半:LINQ とラムダ式でもっと便利に(発展)”

前半で foreach を使って書いた検索・並び替え・集計は、LINQ を使うと数行で短く書けます。

List<Employee> salesEmployees = employees
.Where(employee => employee.DepartmentName == "営業")
.ToList();

ただし、LINQ とラムダ式は少し難しい内容です。まずは「読めて、真似て書ける」ところを目標 にしてください。 難しいと感じたら、いったん前半の List + foreach に戻って構いません。ただし LINQ は第 17 章以降の DB・Web 編で当たり前に登場する ため、第 17 章に入るまでに、基本パターンを真似てでも書けるようにしておくことは必須 です(詳しくは後半の冒頭で説明します)。

後の DB 接続編では、データベース(第 17 章以降の SQLServer など)の employees テーブルから取得したデータを List<Employee> として扱い、検索や集計を行います。 この章は、DB 連携・Web アプリ・デスクトップアプリにつながる重要な準備です。


この章でできるようになること

Section titled “この章でできるようになること”

この章を終えると、次のことができるようになります。

前半:List<T>(まずはここを確実に)

  • 配列と List<T> の違いを説明できる
  • List<int>List<string> を作成し、要素の追加・削除ができる
  • Count で件数を取得し、foreach で全要素を処理できる
  • 自作クラスを T に指定した List<Employee> を扱える
  • foreach で検索・集計(合計・人数・平均)ができる

後半:LINQ とラムダ式(基本パターンは第 17 章までに必須・読めて真似て書ければOK)

  • LINQ の役割を説明できる
  • Where で条件絞り込み、FirstOrDefault で 1 件取得ができる(結果が null になりうることも意識する)
  • ラムダ式の基本的な読み方が分かる
  • OrderByOrderByDescending で並び替えができる
  • SumAverageCount(条件) で集計できる
  • Select で必要な項目だけを取り出せる
  • DB 連携を想定した List<Employee> の使い方を説明できる

項目内容
開発環境Visual Studio 2022
プロジェクト種類コンソール アプリ
対象フレームワーク.NET 8
ソリューション名Chapter12
プロジェクト名Ch12_ListLinq

csproj の Nullable は disable に変更してください

プロジェクト作成後、Ch12_ListLinq.csproj を開き、<Nullable>disable</Nullable> に変更してください。

詳しい手順は、第 1 章「1-1 プロジェクトを作成する」を参照してください。


作業を始める前に、次の内容を確認してください。

  • 配列と foreach 文を使える
  • クラス・プロパティ・コンストラクターを書ける
  • オブジェクト初期化子を使える
  • null チェックを if 文で書ける(第 11 章)
  • 自作クラスを別ファイル(Employee.cs など)に書ける
  • 第 11 章の内容を Git に提出済みである

〔前半〕List<T> だけでできること

Section titled “〔前半〕List<T> だけでできること”

ここから 前半 です。List<T>foreach と組み合わせて使います。 この前半では LINQ は使いません。List だけでも、一覧・検索・集計までできる」ことを体験してください。


List<T> は、同じ型のデータを 複数管理 するための仕組み(コレクション)です。 T の部分には、扱いたい型を指定します。

書き方意味
List<int>int の値を複数管理する
List<string>string の値を複数管理する
List<Employee>Employee のオブジェクトを複数管理する

List<T>System.Collections.Generic 名前空間にありますが、暗黙的 using で取り込まれているため、using を書く必要はありません(第 9 章参照)。


Add メソッドで要素を追加し、foreach で全件処理します。

Program.cs
namespace Ch12_ListLinq;
internal class Program
{
static void Main(string[] args)
{
List<int> scores = new List<int>();
scores.Add(80);
scores.Add(75);
scores.Add(90);
foreach (int score in scores)
{
Console.WriteLine(score);
}
}
}

実行結果:

80
75
90

List<T> の要素数は Count プロパティ で取得します。

List<int> scores = new List<int> { 80, 75, 90 };
Console.WriteLine($"件数:{scores.Count}");

実行結果:

件数:3

配列の Length と対応する位置づけです。どちらもプロパティなので () は付けません。

種類件数取得
配列(int[])scores.Length
List<T>(List<int>)scores.Count

new List<T> { ... } の形で、最初から値を入れて作成できます。

List<string> departmentNames = new List<string>
{
"総務",
"営業",
"開発",
"マーケティング"
};
foreach (string name in departmentNames)
{
Console.WriteLine(name);
}

実行結果:

総務
営業
開発
マーケティング

List<T> も配列と同じく、0 始まりのインデックス で要素にアクセスできます。

List<string> departmentNames = new List<string> { "総務", "営業", "開発" };
Console.WriteLine(departmentNames[0]); // 総務
Console.WriteLine(departmentNames[1]); // 営業
Console.WriteLine(departmentNames[2]); // 開発

指定した値を削除するには Remove を使います。

List<string> departmentNames = new List<string> { "総務", "営業", "開発" };
departmentNames.Remove("営業");
foreach (string name in departmentNames)
{
Console.WriteLine(name);
}

実行結果:

総務
開発

要素の位置(インデックス)を指定して削除する RemoveAt(1) もあります。


項目配列List<T>
要素数作成時に固定後から増減できる
件数取得Length(プロパティ)Count(プロパティ)
追加(専用メソッドなし)Add(値)
削除(専用メソッドなし)Remove(値)RemoveAt(インデックス)
向く場面件数が決まっているデータ件数が変わるデータ

後の DB 接続編では、SELECT 文の結果が何件返ってくるか実行前には分かりません。 そのため、DB から取得したデータは List<T> で扱う のが自然です。


12-2 List<Employee> でオブジェクトを管理する

Section titled “12-2 List<Employee> でオブジェクトを管理する”

List<T>T には、自分で作ったクラスも指定できます。 ここでは、社員情報を表す Employee クラスのリストを作ります。

Employee.cs:

Employee.cs
namespace Ch12_ListLinq;
class Employee
{
public int EmployeeId { get; set; }
public string EmployeeName { get; set; }
public string DepartmentName { get; set; }
public int Salary { get; set; }
}

Program.cs:

Program.cs
namespace Ch12_ListLinq;
internal class Program
{
static void Main(string[] args)
{
List<Employee> employees = new List<Employee>();
employees.Add(new Employee
{
EmployeeId = 1001,
EmployeeName = "山田二郎",
DepartmentName = "総務",
Salary = 500000
});
employees.Add(new Employee
{
EmployeeId = 1002,
EmployeeName = "佐藤昭夫",
DepartmentName = "営業",
Salary = 500000
});
employees.Add(new Employee
{
EmployeeId = 1003,
EmployeeName = "山口洋子",
DepartmentName = "開発",
Salary = 500000
});
foreach (Employee employee in employees)
{
Console.WriteLine($"{employee.EmployeeId}:{employee.EmployeeName}({employee.DepartmentName})");
}
}
}

実行結果:

1001:山田二郎(総務)
1002:佐藤昭夫(営業)
1003:山口洋子(開発)

サンプルデータ作成メソッドを別クラスに分ける

Section titled “サンプルデータ作成メソッドを別クラスに分ける”

毎回 Main の中にサンプルデータを書くと長くなります。 サンプルデータを返す静的メソッド を別クラスに分けておくと、Main がすっきりします。

EmployeeSampleData.cs:

EmployeeSampleData.cs
namespace Ch12_ListLinq;
static class EmployeeSampleData
{
public static List<Employee> Create()
{
return new List<Employee>
{
new Employee { EmployeeId = 1001, EmployeeName = "山田二郎", DepartmentName = "総務", Salary = 500000 },
new Employee { EmployeeId = 1002, EmployeeName = "佐藤昭夫", DepartmentName = "営業", Salary = 500000 },
new Employee { EmployeeId = 1003, EmployeeName = "山口洋子", DepartmentName = "開発", Salary = 500000 },
new Employee { EmployeeId = 1006, EmployeeName = "佐々木明子", DepartmentName = "総務", Salary = 800000 },
new Employee { EmployeeId = 1009, EmployeeName = "星野健一", DepartmentName = "営業", Salary = 400000 }
};
}
}

Program.cs:

Program.cs
namespace Ch12_ListLinq;
internal class Program
{
static void Main(string[] args)
{
List<Employee> employees = EmployeeSampleData.Create();
foreach (Employee employee in employees)
{
Console.WriteLine($"{employee.EmployeeId}:{employee.EmployeeName}({employee.DepartmentName})");
}
}
}

実行結果:

1001:山田二郎(総務)
1002:佐藤昭夫(営業)
1003:山口洋子(開発)
1006:佐々木明子(総務)
1009:星野健一(営業)

以降のサンプルでも、この EmployeeSampleData.Create() で作った List<Employee> を使い回します。


LINQ を学ぶ前に、まずは foreach で社員 1002 を検索してみます。

Program.cs
namespace Ch12_ListLinq;
internal class Program
{
static void Main(string[] args)
{
List<Employee> employees = EmployeeSampleData.Create();
Employee foundEmployee = null;
foreach (Employee employee in employees)
{
if (employee.EmployeeId == 1002)
{
foundEmployee = employee;
break;
}
}
if (foundEmployee == null)
{
Console.WriteLine("社員が見つかりませんでした。");
}
else
{
Console.WriteLine($"{foundEmployee.EmployeeId}:{foundEmployee.EmployeeName}");
}
}
}

実行結果:

1002:佐藤昭夫

このように、foreachif を組み合わせれば検索できます。


foreach で集計してみる(合計・人数・平均)

Section titled “foreach で集計してみる(合計・人数・平均)”

検索だけでなく、合計・人数・平均 のような集計も foreach だけで書けます。

Program.cs
namespace Ch12_ListLinq;
internal class Program
{
static void Main(string[] args)
{
List<Employee> employees = EmployeeSampleData.Create();
int totalSalary = 0;
int salesCount = 0;
foreach (Employee employee in employees)
{
totalSalary += employee.Salary; // 給与を足していく
if (employee.DepartmentName == "営業")
{
salesCount++; // 営業の人数を数える
}
}
int averageSalary = totalSalary / employees.Count;
Console.WriteLine($"給与合計:{totalSalary}");
Console.WriteLine($"平均給与:{averageSalary}");
Console.WriteLine($"営業の人数:{salesCount}");
}
}

実行結果:

給与合計:2700000円
平均給与:540000円
営業の人数:2人

合計は「変数に足し込む」、件数は「条件に合うたびにカウントを増やす」、平均は「合計 ÷ 件数」。 Listforeach だけで、検索も集計もできる ことが分かります。 最後に、ここまでの道具(ListAddRemoveforeachwhileif)を組み合わせて、実際に操作できる小さなプログラム を作ってみましょう。


List で動くミニプログラム(一覧・追加・削除)

Section titled “List で動くミニプログラム(一覧・追加・削除)”

メニューを選んで、品物を 一覧表示・追加・削除 できる「買い物メモ」プログラムを作ります。 ここでも LINQ は使いません。 これまでに学んだ Listforeachwhileif の組み合わせだけで、立派に動くプログラムになります。

Program.cs
namespace Ch12_ListLinq;
internal class Program
{
static void Main(string[] args)
{
List<string> items = new List<string>();
while (true)
{
Console.WriteLine();
Console.WriteLine("=== 買い物メモ ===");
Console.WriteLine("1: 一覧表示 2: 追加 3: 削除 0: 終了");
Console.Write("> ");
string input = Console.ReadLine();
if (input == "1")
{
if (items.Count == 0)
{
Console.WriteLine("(メモは空です)");
}
else
{
foreach (string item in items)
{
Console.WriteLine($"- {item}");
}
}
}
else if (input == "2")
{
Console.Write("追加する品物:");
string name = Console.ReadLine();
items.Add(name);
Console.WriteLine($"{name}」を追加しました。");
}
else if (input == "3")
{
Console.Write("削除する品物:");
string name = Console.ReadLine();
if (items.Remove(name))
{
Console.WriteLine($"{name}」を削除しました。");
}
else
{
Console.WriteLine($"{name}」は見つかりませんでした。");
}
}
else if (input == "0")
{
Console.WriteLine("終了します。");
break;
}
else
{
Console.WriteLine("0〜3 を入力してください。");
}
}
}
}

実行結果例(入力した部分は太字相当):

=== 買い物メモ ===
1: 一覧表示 2: 追加 3: 削除 0: 終了
> 2
追加する品物:りんご
「りんご」を追加しました。
=== 買い物メモ ===
1: 一覧表示 2: 追加 3: 削除 0: 終了
> 2
追加する品物:牛乳
「牛乳」を追加しました。
=== 買い物メモ ===
1: 一覧表示 2: 追加 3: 削除 0: 終了
> 1
- りんご
- 牛乳
=== 買い物メモ ===
1: 一覧表示 2: 追加 3: 削除 0: 終了
> 3
削除する品物:りんご
「りんご」を削除しました。
=== 買い物メモ ===
1: 一覧表示 2: 追加 3: 削除 0: 終了
> 0
終了します。

仕組みを分解すると、すべて既習の組み合わせです。

部分使っているもの
メニューを繰り返すwhile (true)break(0 で抜ける)第 5 章
選択肢の判定if / else if と文字列の比較(input == "1")第 4 章
一覧表示foreach で全件、Count で空判定第 12 章
追加items.Add(name)第 12 章
削除items.Remove(name)(削除できれば true を返す)第 12 章

ポイント:List が「アプリの中身」を覚えている

プログラムが動いている間、items という 1 つの Listデータを保持し続けます。 「追加」で増え、「削除」で減り、「一覧」で今の中身を見せる ── これは、第 23 章以降で作る 社員管理アプリ(一覧・追加・削除) とまったく同じ考え方です。 今は文字列のリストですが、List<Employee> に変えれば、そのまま「社員名簿アプリ」になります(演習の総合課題で挑戦します)。


〔前半のまとめ〕List だけでも、これだけできる

List<T>foreach と組み合わせるだけで、一覧表示・検索・集計(合計 / 人数 / 平均)、さらに メニュー型のミニプログラム(一覧・追加・削除) まで作れます。 業務でも、まずはこの List + foreach が基本の道具です。ここまでを確実にできれば十分 です。


〔後半〕LINQ とラムダ式 ― もっと便利に(発展・少し難しい)

Section titled “〔後半〕LINQ とラムダ式 ― もっと便利に(発展・少し難しい)”

ここから 後半 です。前半で foreach を使って書いた検索・並び替え・集計を、LINQ を使って短く書く方法を学びます。

ただし LINQ とラムダ式は少し難しい内容です。まずは「読めて、真似て書ける」ことを目標 にしてください。 難しいと感じたら、いったん前半の List + foreach に戻って大丈夫です。

【重要】LINQ は第 17 章までに「真似てでも使える」ように

ラムダ式の細かい仕組みまで完全に理解できていなくて構いません。しかし、第 17 章(データベース接続)以降では、LINQ を使ったコードが当たり前に登場します第 17 章に入るまでに、基本パターン(Where / Select / Sum)を真似てでも書けるようになっておくことは必須 です。 今日この場で完璧でなくても大丈夫です(難しければ前半に戻ってOK)。ただしその場合も、自習日や復習で 必ずこのパターンを身につけてください。「foreach で十分だから LINQ は要らない」ではなく、「今は foreach で乗り切り、LINQ は後で必ず使えるようにする」と考えてください。


LINQ(リンク, Language INtegrated Query)は、コレクション(List<T> や配列など)に対して、検索・絞り込み・並び替え・集計を メソッド呼び出しで簡潔に書く ための仕組みです。

裏では特別なことは起きていません。次のようなイメージで動いています。

employees.Where(条件).ToList()
↓ 裏で行われていること
foreach で 1 件ずつ取り出して、条件が true のものを集める
最後に .ToList() で List<T> として確定させる

つまり LINQ は、前の節で書いた「foreach + if + 別の List に詰める」というパターンを、短く書くためのショートカット です。 書き方は変わりますが、行われていることは普段の繰り返し処理と同じです。

LINQ は System.Linq 名前空間にありますが、暗黙的 using で取り込まれているため using を書く必要はありません


Where は、条件に合う要素だけを取り出します。

Program.cs
namespace Ch12_ListLinq;
internal class Program
{
static void Main(string[] args)
{
List<Employee> employees = EmployeeSampleData.Create();
List<Employee> salesEmployees = employees
.Where(employee => employee.DepartmentName == "営業")
.ToList();
foreach (Employee employee in salesEmployees)
{
Console.WriteLine($"{employee.EmployeeId}:{employee.EmployeeName}({employee.DepartmentName})");
}
}
}

実行結果:

1002:佐藤昭夫(営業)
1009:星野健一(営業)

foreach で書いたコードと比べて、ぐっと短くなりました。


ラムダ式とは(まずはイメージから)

Section titled “ラムダ式とは(まずはイメージから)”

Where の中に書いている employee => employee.DepartmentName == "営業"ラムダ式 と呼ばれます。

ラムダ式は、ひとことで言うと 「名前を付けずに書いた、小さなメソッド」 です。

もし普通のメソッドとして書くなら、次のようになります。

static bool IsSales(Employee employee)
{
return employee.DepartmentName == "営業";
}

これと同じことを、Where の中だけで使い捨てる目的で 短く書いた のがラムダ式です。

employee => employee.DepartmentName == "営業"

両者を並べて比べると、対応関係が見えます。

メソッド式ラムダ式
メソッド名 IsSales(省略・名前なし)
戻り値の型 bool(省略・自動で判定される)
引数 Employee employeeemployee(型も省略可)
{ return ...; }=> ...(式だけ書く)

このように、ラムダ式は メソッドを書く手間をぐっと省いた書き方 です。 わざわざ別の場所にメソッドを定義しなくても、Where の引数として その場で 1 行 で渡せます。


LINQ で出てくるラムダ式は 3 パターンだけ

Section titled “LINQ で出てくるラムダ式は 3 パターンだけ”

最初は「LINQ メソッドの中で使うお決まりの書き方」と割り切るのが安全です。 本章で出てくるラムダ式は、次の 3 パターンに分類できます。

用途返すもの使うメソッド
条件判定bool(真偽値)e => e.Salary >= 500000WhereFirstOrDefaultCount(条件)
並び替えのキー比較に使う値e => e.SalaryOrderByOrderByDescending
変換・取り出し取り出したい値e => e.EmployeeNameSelectSumAverage

ラムダ式そのものは、デリゲートや Func<T, bool> 型といった少し深い概念と結び付いていますが、本研修では深入りしません。 「LINQ メソッドの引数として、その場で使い捨てる小さな式」 くらいの理解で、後の DB 接続編・Web MVC 編でも十分に書けます。


Where の中の式をもう一度見てみます。

employee => employee.DepartmentName == "営業"
部分意味
employeeリストから 1 件ずつ取り出した要素を入れる変数
=>「を使って」「を以下のように評価する」
employee.DepartmentName == "営業"この式が true の要素だけ通す

日本語にすると:

employee(1 件分の社員)を見て、
DepartmentName が "営業" なら通す

リストの各要素に対して、この式が true になるものだけWhere の結果に含まれます。


Where の戻り値は List<T> そのものではなく、「並び」を表す中間データ(IEnumerable<T>)です。 これを List<T> として受け取るには、最後に .ToList() を付けます。

List<Employee> salesEmployees = employees
.Where(employee => employee.DepartmentName == "営業")
.ToList(); // ← List<Employee> に変換

ひとまずは「LINQ の結果は .ToList()List<T> にする」と覚えればよいです。


FirstOrDefault は、条件に合う 最初の 1 件 を取得します。見つからなかった場合は null を返します。

Program.cs
namespace Ch12_ListLinq;
internal class Program
{
static void Main(string[] args)
{
List<Employee> employees = EmployeeSampleData.Create();
Employee employee = employees
.FirstOrDefault(e => e.EmployeeId == 1002);
if (employee == null)
{
Console.WriteLine("社員が見つかりませんでした。");
}
else
{
Console.WriteLine($"{employee.EmployeeId}:{employee.EmployeeName}");
}
}
}

実行結果:

1002:佐藤昭夫

FirstOrDefault「OrDefault」 が「見つからなければ既定値(参照型なら null)を返す」という意味です。 戻り値を使う前に、必ず null チェック を行いましょう。

補足:類似メソッド First

First も最初の 1 件を取得しますが、見つからない場合は 例外 を投げます。 「見つからない可能性がある」場合は FirstOrDefault の方が安全です。


LINQ の Count(条件) で、条件に合う件数を数えられます。

List<Employee> employees = EmployeeSampleData.Create();
int salesCount = employees
.Count(e => e.DepartmentName == "営業");
Console.WriteLine($"営業の人数:{salesCount}");

実行結果:

営業の人数:2人

Count には 2 種類の使い方があるので注意してください。

書き方種類意味
employees.Countプロパティリスト全体の件数
employees.Count(条件)LINQ メソッド条件に合う件数

() の有無で別物だと意識しましょう。


12-4 LINQ で並び替え・集計を行う

Section titled “12-4 LINQ で並び替え・集計を行う”

これ以降の例の書く場所について

ここから先のコードも、これまでと同じく Main の中 に書きます。最初に List<Employee> employees = EmployeeSampleData.Create(); で社員リストを用意し、その下に LINQ のコードを書く前提です(Main を含む完全な形は、12-3「Where で絞り込む」「FirstOrDefault で 1 件取得する」の例を参照)。 以降は、注目してほしい LINQ の部分だけ を抜き出して短く示します。

OrderBy で、指定した項目で 昇順(小さい順) に並び替えます。

List<Employee> employees = EmployeeSampleData.Create();
List<Employee> sorted = employees
.OrderBy(e => e.Salary)
.ToList();
foreach (Employee employee in sorted)
{
Console.WriteLine($"{employee.EmployeeName}:{employee.Salary}");
}

実行結果:

星野健一:400000円
山田二郎:500000円
佐藤昭夫:500000円
山口洋子:500000円
佐々木明子:800000円

OrderByDescending で降順に並び替える

Section titled “OrderByDescending で降順に並び替える”

降順(大きい順) にしたい場合は OrderByDescending を使います。

List<Employee> sorted = employees
.OrderByDescending(e => e.Salary)
.ToList();

実行結果:

佐々木明子:800000円
山田二郎:500000円
佐藤昭夫:500000円
山口洋子:500000円
星野健一:400000円
メソッド並び順
OrderBy(項目)昇順(小さい順、古い順、あいうえお順)
OrderByDescending(項目)降順(大きい順、新しい順、その逆)

Sum で、指定した数値項目の合計を求められます。

List<Employee> employees = EmployeeSampleData.Create();
int totalSalary = employees.Sum(e => e.Salary);
Console.WriteLine($"給与合計:{totalSalary}");

実行結果:

給与合計:2700000円

Average で平均を求められます。戻り値は double です。

double averageSalary = employees.Average(e => e.Salary);
double rounded = Math.Round(averageSalary, 1);
Console.WriteLine($"平均給与:{rounded}");

実行結果:

平均給与:540000円

桁数を抑えたい場合は、第 9 章で学んだ Math.Round と組み合わせます。


Select で必要な項目だけ取り出す

Section titled “Select で必要な項目だけ取り出す”

Select で、各要素から 必要な項目だけ を取り出して新しいリストにできます。

List<Employee> employees = EmployeeSampleData.Create();
List<string> employeeNames = employees
.Select(e => e.EmployeeName)
.ToList();
foreach (string name in employeeNames)
{
Console.WriteLine(name);
}

実行結果:

山田二郎
佐藤昭夫
山口洋子
佐々木明子
星野健一

Select は、画面表示用に必要な項目だけを取り出す場面や、形を変換する場面で使います。


Where の条件は、&&(かつ)・||(または)で組み合わせられます。 次の例は、部署が “営業” かつ 給与が 500000 円以上 の社員を取り出します。

List<Employee> results = employees
.Where(e => e.DepartmentName == "営業" && e.Salary >= 500000)
.ToList();
foreach (Employee employee in results)
{
Console.WriteLine($"{employee.EmployeeName}:{employee.Salary}");
}

実行結果:

佐藤昭夫:500000円

検索結果が 0 件の場合もあります。.Count で確認して、ユーザーに伝えます。

List<Employee> results = employees
.Where(e => e.DepartmentName == "人事")
.ToList();
if (results.Count == 0)
{
Console.WriteLine("該当する社員は見つかりませんでした。");
}
else
{
foreach (Employee employee in results)
{
Console.WriteLine(employee.EmployeeName);
}
}

実行結果:

該当する社員は見つかりませんでした。

Web アプリ・デスクトップアプリでも、検索結果 0 件の表示は重要なポイントです。


stringContains を組み合わせると、部分一致検索 ができます。

Console.Write("検索する名前を入力してください:");
string keyword = Console.ReadLine();
List<Employee> results = employees
.Where(e => e.EmployeeName.Contains(keyword))
.ToList();
foreach (Employee employee in results)
{
Console.WriteLine($"{employee.EmployeeId}:{employee.EmployeeName}");
}

実行例:

検索する名前を入力してください:山
1001:山田二郎
1003:山口洋子

12-5 LINQ をもっと使いこなすために

Section titled “12-5 LINQ をもっと使いこなすために”

ここまで、LINQ の基本的なメソッドの使い方を 1 つずつ学んできました。 本節では、現場で書くときに役立つ 整理の視点 を 4 つ紹介します。細かい暗記は不要で、「こういうものがある」と知っておくだけで、後章のコード読みが楽になります。


メソッドチェーンで段階的に処理する

Section titled “メソッドチェーンで段階的に処理する”

LINQ のメソッドは つなげて書く(メソッドチェーン)ことで威力を発揮します。 たとえば「営業部の社員を給与の高い順に並べて、氏名だけ取り出す」を書くと次のようになります。

List<string> result = employees
.Where(e => e.DepartmentName == "営業") // ① 絞り込み
.OrderByDescending(e => e.Salary) // ② 並び替え
.Select(e => e.EmployeeName) // ③ 必要項目だけ取り出し
.ToList(); // ④ List<string> に確定

裏では次のような中間データが順に渡されていきます。

employees(5 件)
↓ Where で絞り込み
[佐藤昭夫, 星野健一](2 件)
↓ OrderByDescending で並び替え
[佐藤昭夫(500000), 星野健一(400000)]
↓ Select で氏名だけに変換
["佐藤昭夫", "星野健一"]
↓ ToList で確定
List<string>

各メソッドが 前のメソッドの結果を受け取り、次に渡す 仕組みです。 順番が大事で、OrderByWhere の前に置くと「全件並び替えてから絞り込む」になってしまい、無駄な処理が増えます。 慣れないうちは 絞り込み → 並び替え → 取り出し → ToList の順で覚えると安全です。


本章で扱ったメソッドを中心に、現場でよく出るメソッドも含めて整理します。

分類メソッド役割本章
絞り込みWhere(条件)条件に合う要素だけ通す
1 件取得FirstOrDefault(条件)最初の 1 件、なければ null
1 件取得First(条件)最初の 1 件、なければ例外
件数Count(条件)条件に合う件数
存在チェックAny(条件)1 件でも合えば true
全件チェックAll(条件)すべてが合えば true
並び替えOrderBy(キー)昇順
並び替えOrderByDescending(キー)降順
第二キーThenBy(キー)OrderBy の後の同点処理
集計Sum(キー)合計
集計Average(キー)平均
集計Min(キー) / Max(キー)最小 / 最大
取り出しSelect(変換)各要素を変換
確定ToList()List<T> に変換
確定ToArray()配列に変換

は本章で扱い、 は補足程度に触れ、 は本章では扱いません。 のメソッドは、必要になったときに名前で検索すれば使い方がすぐに見つかります。全部を今覚える必要はありません


LINQ は便利ですが、何でも LINQ で書けばよい わけではありません。 場面によっては foreach のほうが読みやすいことがあります。

場面おすすめ理由
単純な絞り込み・並び替え・集計LINQ短く読みやすい
各要素ごとに副作用がある処理(画面表示、ログ出力、DB 書き込み)foreachリストを作るのが目的ではないため
集計と表示を同時に行うなど、複数の処理を 1 周で済ませたいforeachLINQ に詰め込むと読みにくくなる
中断が必要(break、途中で return)foreachLINQ では基本的に中断できない
例外処理を細かく挟みたいforeachLINQ の中に try-catch は書きにくい

ひとつの目安:「リスト → リスト」「リスト → 1 件 / 数値」のような データの変換・集計 は LINQ、「リストの各要素に対して何かを実行する」は foreach


LINQ には、本章で扱った メソッド構文 の他に、SQL に似た クエリ構文 という書き方もあります。

// メソッド構文(本章で学んだ書き方)
List<Employee> sales = employees
.Where(e => e.DepartmentName == "営業")
.OrderBy(e => e.Salary)
.ToList();
// クエリ構文
List<Employee> sales = (
from e in employees
where e.DepartmentName == "営業"
orderby e.Salary
select e
).ToList();

from ... in ... where ... orderby ... select ... の構文は SQL の SELECT 文と並びが似ている ため、SQL 研修受講者には読みやすく感じるかもしれません。

ただし、現場では圧倒的にメソッド構文のほうが多く使われます。本研修でもメソッド構文を基本としますが、現場のコードでクエリ構文を見たときに驚かないよう、ここで紹介しておきます。

両者は最終的に同じ結果になり、コンパイラがクエリ構文をメソッド構文に変換しています。


12-6 DB 連携に向けた List<T> の考え方

Section titled “12-6 DB 連携に向けた List<T> の考え方”

後の DB 接続編では、データベースの employees テーブルから取得したデータを、次のように List<Employee> で扱います。

employees テーブル(DB)
employee_id | employee_name | department_id | salary
------------|---------------|---------------|-------
1001 | 山田二郎 | 1 | 500000
1002 | 佐藤昭夫 | 2 | 500000
1003 | 山口洋子 | 3 | 500000

上記のイメージは「列名と値の組み合わせ」を分かりやすくするための簡略表記です。第 17 章で扱う SQLServer のテーブルでは last_name(姓)と first_name(名)を分けて持ちますが、ここでは LINQ の流れに集中するため 1 列(employee_name)にまとめてあります。

SELECT 結果
↓ 1 行ずつ読み取る
new Employee { EmployeeId = ..., EmployeeName = ..., ... }
↓ Add
List<Employee> employees
↓ LINQ
検索・絞り込み・並び替え・集計

つまり、DB 接続編に進む前に List<Employee> と LINQ に慣れておく ことが重要です。 この章で学んだ書き方は、DB 連携でもそのまま使えます。


Web・デスクトップアプリでの利用イメージ

Section titled “Web・デスクトップアプリでの利用イメージ”

List<Employee> は、画面表示でも頻繁に使います。

  • Web アプリ:Controller から View へ List<Employee> を渡し、一覧表示する
  • デスクトップアプリ:List<Employee>DataGridViewDataSource に設定して表形式で表示する
データベース(SQLServer / Oracle)
List<Employee>
画面の一覧(Web の表、Forms の DataGridView)

この章の内容は、研修後半のアプリ作成にそのままつながります。


つまずき原因対応
List<T>T が分からないジェネリック型に慣れていないT には扱う型を書く(List<int>List<Employee>)
Add を忘れて空のリストになるリストに要素を追加していないlist.Add(...) を確認
CountCount() を混同プロパティと LINQ メソッドが混ざる全件は Count、条件付きは Count(条件)
Where の結果を List<T> に代入できないToList() を忘れている.Where(...).ToList() と書く
ラムダ式が読めない=> の意味に慣れていない「1 件取り出して条件を見る」と読む
FirstOrDefault の結果でエラー見つからず null なのにプロパティを使っている必ず null チェック
部分一致検索で大文字小文字が違うContains は大小文字を区別する必要なら ToLower() で揃える
OrderBy の結果が並び替わらないToList() で受けていない必ず .ToList() を付ける
LINQ を 1 行に詰め込みすぎる読みにくい.Where(...).OrderBy(...).ToList() のように改行する

前半:List<T>(まずはここを確実に)

  • List<T> とは何かを説明できる
  • 配列と List<T> の違いを説明できる
  • List<int>List<string>List<Employee> を作成できる
  • AddCountRemove を使える
  • サンプルデータ用の静的クラス・メソッドを書ける
  • foreach で検索・集計(合計・人数・平均)ができる

後半:LINQ とラムダ式(できれば)

  • LINQ とは何かを説明できる
  • Where で絞り込みができる
  • ラムダ式の基本的な読み方が分かる
  • ToList() の役割を説明できる
  • FirstOrDefault で 1 件取得し、null チェックできる
  • OrderByOrderByDescending で並び替えできる
  • SumAverageCount(条件) で集計できる
  • Select で必要な項目だけを取り出せる
  • DB の検索結果を List<Employee> で扱うイメージを説明できる

研修の進め方によっては、隣の人またはチーム内で説明確認を行います。

次の内容を、自分の言葉で説明してください。

  1. 配列と List<T> の違いは何ですか。
  2. List<Employee> は何を表していますか。
  3. employees.Countemployees.Count(条件) の違いは何ですか。
  4. Where はどんなときに使いますか。
  5. ラムダ式 employee => employee.Salary >= 500000 はどう読みますか。
  6. FirstOrDefault の結果を使う前に注意することは何ですか。
  7. Select はどんなときに使いますか。
  8. DB から取得した複数件のデータを、C# では何で受けますか。

説明するときは、完全な答えでなくても構いません。 自分の言葉で説明しようとすることが大切です。


この章の演習課題に取り組みます。

本章では タイマー方式 で進めます。さらに本章から、ソロ作業の前に 設計タイム(コメント・枠づくり) を試験導入します。次の 3 段階で進めてください。

段階時間内容
① 設計タイム15 分(評価対象外)ペア確認・課題(仕様)の読み込みに加え、コメントとクラス/メソッドの枠づくり を行う。ペアや講師に相談してよい(詳細は下記)
② ソロ作業30 分(タイマーで計測)一人で課題に取り組む。枠の下に実装ロジックを書く。タイマーが鳴ったら、完成・未完成にかかわらず作業を止めて提出する
③ チーム時間講師が指定する発表開始時刻までチーム内でコードレビューを行い、発表者を決める。実装の続行も可。時間配分はチームで管理する

① 設計タイムで作ってよいもの(評価対象外・ペアで相談OK)

ソロのタイマーに入る前に、ペアやグループで相談しながら 次まで作ってよい です(作ったコメント・枠は ソロのコミットにそのまま残してOK)。

  • ヘッダーコメント(課題番号・目的・氏名。第 7 章コラム参照)
  • 処理の流れを示す 意図コメント(例:// products を Where で文房具だけに絞る)
  • クラス・メソッドの (Product クラスのプロパティ宣言、メソッドのシグネチャと空の { })
  • プロジェクト・クラスファイル(Product.csProductSampleData.cs など)の用意

ただし 枠を作ってよいのは「問題文が指定している範囲」まで です。必須課題(12-2・12-3)は Product のプロパティや扱うデータが問題文の表で示されているので、その枠は写して作って構いません。発展課題(12-4〜12-7)で どんなクラス・処理に分けるかを自分で考える部分は、設計タイムでは意図コメント(処理の流れのメモ)までにとどめ、枠はソロで 作ってください。迷ったら「その枠は問題文に書いてあるか?」で判断します。

タイマーで測るのは、枠の下に各自が書く「実装ロジック(LINQ や foreach の中身)」 です。設計はみんなで考え、実装はひとりで書きます。詳しくは 付録 A「演習サイクルとチームレビューの進め方」A-2 を参照してください。

第 12 章は LINQ メソッドを多用し、課題ごとにクラス(Product.cs など)とサンプルデータ用クラス(ProductSampleData.cs など)を別ファイルで作る作業が発生します。設計タイムでこれらの枠とサンプルデータの用意まで済ませておくと、ソロでは LINQ・foreach の中身に集中できます。設計タイムの終わりに講師が号令をかけ、そこからソロのタイマー(30 分)を開始します。 評価対象はタイマー時点で提出されたコードです(タイマー後に書き足した分は評価には含まれません)。設計タイムで作ったコメント・枠は、ソロのコミットに含めて構いません。 発表開始時刻は厳守 です。チーム時間中も、その時刻が来たら全員手を止めて発表に移ります。

演習の進め方の詳細は、付録A「演習の進め方」 を参照してください。


課題はソリューション Kadai12 の中に作成してください。 課題ごとに別のプロジェクトを作成 し、指定されたプロジェクト名を使います。

課題必須/発展プロジェクト名作成する主なファイル
課題 12-1必須Kd12_01_ListBasicProgram.cs
課題 12-2必須Kd12_02_ProductListSearchProgram.csProduct.csProductSampleData.cs
課題 12-3必須Kd12_03_SortAndAggregateProgram.csProduct.csProductSampleData.cs
課題 12-4発展Kd12_04_PartialMatchProgram.csProduct.csProductSampleData.cs
課題 12-5発展Kd12_05_SelectAndFilterProgram.csProduct.csProductSampleData.cs
課題 12-6発展Kd12_06_DbStyleEmployeeProgram.csEmployee.cs
課題 12-7総合・挑戦Kd12_07_EmployeeRosterProgram.csEmployee.csEmployeeSampleData.cs

フォルダ構成は次のようになります。

Kadai12/ ← 課題用ソリューションフォルダ
Kadai12.sln
Kd12_01_ListBasic/
Kd12_02_ProductListSearch/
Kd12_03_SortAndAggregate/
Kd12_04_PartialMatch/
Kd12_05_SelectAndFilter/
Kd12_06_DbStyleEmployee/
Kd12_07_EmployeeRoster/

補足:ソリューションに複数のプロジェクトを追加する方法

最初の課題で Kadai12 ソリューションと Kd12_01_ListBasic プロジェクトを同時に作成します。

2 つ目以降の課題は、ソリューションエクスプローラーで Kadai12 を右クリックし、追加新しいプロジェクト から追加します。


以下は、本章のすべての課題に共通する作業です。各課題の本文には繰り返し書きません。

作業内容参照
プロジェクト作成時の設定最上位レベルのステートメントを使用しない」にチェック第 1 章 1-1
csproj の編集<Nullable>disable</Nullable> に変更第 1 章 1-1
namespace の書き方ファイルスコープ形式(namespace XXX;)で書く第 7 章 冒頭
クラスファイルの追加ソリューションエクスプローラーでプロジェクトを右クリック → 追加クラス第 7 章 7-7
ファイルを保存して実行Ctrl + S で保存 → F5 で実行第 1 章 1-2

特に 2 つ目以降のプロジェクト(Kd12_02_ProductListSearch など)も、新規追加するたびに csproj の Nullable を disable に変更する 必要があります。

クラスファイルを自動生成すると、ブロック形式の namespace プロジェクト名 { ... } で作られます(例:課題 12-2 なら namespace Kd12_02_ProductListSearch { ... })。本章でもファイルスコープ形式に統一するため、生成後に namespace Kd12_02_ProductListSearch; のように ; 形式に書き換えてください。同じプロジェクト内の Program.cs とクラスファイル(Product.csProductSampleData.cs など)の namespace は揃えます。

提出ルール(タイマー方式)

30 分のタイマーが鳴ったら手を止め、git addcommitpush を行います。評価対象はタイマー時点のコミットのみです(タイマー後の追加は評価外)。

Chapter12 タイマー提出: <どこまで完成> / <詰まったポイント>

例:Chapter12 タイマー提出: 12-1〜12-2完成、12-3は途中 / OrderByDescending と ToList の書き方で詰まった

サーバへコピー提出の場合

Git 不調で講師指示があれば、push の代わりに Kadai12 フォルダをサーバへコピーし、提出先に 提出メモ.txt を作成します(内容:どこまで完成 / 詰まったポイント)。

タイマー後のチーム時間

発表開始時刻まで、コードレビュー / 発表者選出 / 実装続行(任意・評価外)。時間配分はチームの判断、発表開始時刻は厳守


まずは、全員が必須課題に取り組んでください。

必須課題の進め方

必須課題は、並び替えを除いて foreach だけで一通り実装できます。LINQ がまだ不安な人は、まず foreach で完成させてください。 LINQ(Where / FirstOrDefault / Sum など)で書ければ、なお良し です。両方書いて見比べると、LINQ の短さが実感できます。 (並び替えだけは、foreach で自前実装すると大変なので OrderByDescending を使います。LINQ が一番活きる処理です。)


課題 12-1 List で点数と部署を管理する

Section titled “課題 12-1 List で点数と部署を管理する”

List<int> で点数、List<string> で部署名を管理するプログラムを作成してください。

処理内容

  1. List<int>80, 75, 90, 68, 85 を追加し、Count で件数を表示してから全件を表示する
  2. List<string> を初期値あり(総務, 営業, 開発, マーケティング)で作成し、Count で件数を表示してから全件を表示する

実行結果例:

[点数]
件数:5
80
75
90
68
85
[部署]
件数:4
総務
営業
開発
マーケティング

条件:

  • List<int> には Add で追加する
  • List<string> は初期値ありで作成する
  • どちらも Count プロパティで件数を表示する
  • foreach で全件表示する

Product(商品)クラスと、サンプルデータ作成用の ProductSampleData 静的クラスを作成し、商品を検索してください。 foreach + if で実装してOK。余裕があれば Where / FirstOrDefault でも書いてみましょう。

Product クラスのプロパティ

プロパティ名意味
ProductIdint商品番号
ProductNamestring商品名
Categorystringカテゴリ
Priceint価格(円)
Stockint在庫数

ProductSampleData.Create() で返す 6 件分のデータ

ID商品名カテゴリ価格在庫
1ノート文房具18050
2ボールペン文房具120100
3サインペン文房具20040
4マグカップ食器80020
5タンブラー食器17008
6Tシャツ衣類240015

Main メソッドの処理

  1. ProductSampleData.Create()List<Product> を取得
  2. カテゴリが「食器」の商品を抽出して全件表示(foreach + if でよい / Where でも可)
  3. ProductId == 4 の商品を 1 件取得し、見つかれば商品名を表示、見つからなければ「商品が見つかりませんでした。」と表示(foreach + if でよい / FirstOrDefault でも可)

実行結果例:

[食器の商品]
4:マグカップ
5:タンブラー
[ID 4 検索]
マグカップ

条件:

  • Product.csProductSampleData.cs を別ファイルに作成
  • まずは foreach + if で実装してよい(食器の抽出・ID 検索)
  • 余裕があれば Where / FirstOrDefault でも書いてみる(同じ結果を短く書けることを確認)
  • 見つからない場合(該当なし / null)の処理を必ず入れる

課題 12-2 の ProductSampleData を使い、価格で並び替えと集計を行ってください。

処理内容

  1. 価格の降順 で全商品を表示 ← 並び替えは OrderByDescending を使う(foreach で自前ソートは大変。ここは LINQ が圧倒的に簡単)
  2. 在庫の合計 を表示 ← foreach で足し込んでよい(Sum ならなお良し)
  3. 平均価格 を表示 ← 合計 ÷ 件数 で求める(Average ならなお良し)

実行結果例:

[価格の高い順]
Tシャツ:2400円
タンブラー:1700円
マグカップ:800円
サインペン:200円
ノート:180円
ボールペン:120円
在庫合計:233個
平均価格:900円

条件:

  • 並び替えは OrderByDescending + ToList() を使う(LINQ が圧倒的に簡単な処理。ここだけは LINQ)
  • 集計(在庫合計・平均価格)は foreach でも Sum / Average でもよい
  • 平均に小数が出る場合は Math.Round で調整してよい(今回のデータは割り切れる)

必須課題が終わった人は、発展課題に取り組んでください。 発展課題からは、仕様だけが提示されます。実装方法は自分で考えてください。

発展課題は LINQ の練習場です

発展課題は、後半で学んだ LINQ(Where / Select / Sum など)を使って書く 練習として取り組んでください。 foreach でも書けますが、ここでは「LINQ で短く書く」ことに挑戦するのが目的です。


課題 12-4 商品名の部分一致検索

Section titled “課題 12-4 商品名の部分一致検索”

以下の仕様で、商品名にキーワードを含む商品を検索するプログラムを作成してください。

仕様

  • Console.ReadLine で検索キーワードを 1 行受け取る
  • 前後の空白を Trim で取り除く
  • Where + Contains で部分一致検索を行う
  • 検索結果が 0 件なら「該当する商品は見つかりませんでした。」と表示
  • 1 件以上なら、ProductId の昇順で並び替えてから一覧表示

サンプルデータは課題 12-2 と同じ(ProductSampleData.Create() を再利用)。

実行例:

検索キーワードを入力してください:ペン
2:ボールペン(文房具)
3:サインペン(文房具)
検索キーワードを入力してください:時計
該当する商品は見つかりませんでした。

課題 12-5 Select で必要な項目だけ取り出す + 複数条件

Section titled “課題 12-5 Select で必要な項目だけ取り出す + 複数条件”

以下の仕様でプログラムを作成してください。

仕様

サンプルデータは課題 12-2 と同じ。 次の 2 つを順番に行う。

  1. 複数条件絞り込み:カテゴリが「文房具」かつ 価格が 150 円以上の商品を、Where で絞り込み、ProductNamePrice を表示する
  2. Select で商品名のみ取り出し:全商品から Select を使って ProductName だけの List<string> を作り、foreach で全件表示する

実行結果例:

[文房具 かつ 価格150以上]
ノート:180円
サインペン:200円
[全商品の商品名]
ノート
ボールペン
サインペン
マグカップ
タンブラー
Tシャツ

条件:

  • Where の条件で && を使う
  • SelectList<string> を作る

課題 12-6 DB 接続を想定した Employee リスト

Section titled “課題 12-6 DB 接続を想定した Employee リスト”

後の DB 接続編を想定し、null 許容値型を含む Employee クラスとそのリスト処理を作成してください。

Employee クラス仕様

プロパティ名意味
EmployeeIdint社員番号
EmployeeNamestring氏名
HireDateDateTime入社日
Salarydecimal給与
Commissiondecimal?歩合(null あり)
ManagerIdint?上司の社員番号(null あり)
DepartmentIdint部署番号

引数なしの自動プロパティでよい(オブジェクト初期化子で作成する想定)。

サンプルデータ(List<Employee>)

ID氏名入社日給与歩合上司ID部署ID
1001山田二郎2001-04-01500000nullnull1
1002佐藤昭夫2003-04-01500000150000010012
1006佐々木明子1995-04-018000002000000null1

Main メソッドの処理

  1. 全社員を EmployeeId:EmployeeName:給与{Salary}円:歩合{歩合 or 未設定} の形で一覧表示
  2. 歩合がある社員だけ(Commission != null)を Where で抽出して表示
  3. 全社員の歩合の合計 を求めて表示(Sum、null の場合は 0 として合算する)

実行結果例:

[全社員]
1001:山田二郎:給与500000円:歩合未設定
1002:佐藤昭夫:給与500000円:歩合1500000円
1006:佐々木明子:給与800000円:歩合2000000円
[歩合あり社員]
1002:佐藤昭夫:1500000円
1006:佐々木明子:2000000円
歩合合計:3500000円

条件:

  • decimal?int? を使う
  • null の表示には ?? または if 文を使う
  • Sum の中で null を 0 として扱う(Sum(e => e.Commission ?? 0))

課題 12-7 【総合・挑戦】社員名簿ミニプログラム(一覧・追加・削除 + LINQ で機能追加)

Section titled “課題 12-7 【総合・挑戦】社員名簿ミニプログラム(一覧・追加・削除 + LINQ で機能追加)”

本文「List で動くミニプログラム(買い物メモ)」の 社員版 を作ります。 List<Employee> を使い、社員の 一覧表示・追加・削除 ができるメニュー型プログラムを作成してください。 さらに、LINQ を使うと「検索・並び替え・集計」メニューを増やせます

これは総合課題(挑戦)です。本章の前半と後半を 1 つに束ねます

基本の 一覧・追加・削除は List + foreach だけ で作れます(前半の力)。 そこに LINQ を足すと、検索・並び替え・平均などの便利メニューを増やせます(後半の力)。LINQ を真似て使う、絶好の練習です。 規模が大きいので、時間内に完成しなくても OK。まず基本(1〜3)を動かし、余裕があれば LINQ メニュー(4〜6)に挑戦してください。完成すれば、第 23 章以降で作る社員管理アプリの コンソール版 を自力で書いたことになります。

Employee クラス(課題 12-2 と同じ)

プロパティ名
EmployeeIdint
EmployeeNamestring
DepartmentNamestring
Salaryint

メニューと動作

=== 社員名簿 ===
[基本] 1: 一覧表示 2: 追加 3: 削除
[LINQ] 4: 部署で検索 5: 給与の高い順 6: 平均給与
0: 終了
> _

基本(必達・List + foreach で作れる)

  • 起動時に、初期データとして EmployeeSampleData.Create() の社員を入れておいてよい
  • 1 一覧表示:全社員を 1001:山田二郎(総務)500000円 の形で 1 行ずつ表示(foreach)。0 件なら「(名簿は空です)」
  • 2 追加:社員番号・氏名・部署・給与を順に入力し、new Employee { ... } を作って Add
  • 3 削除:社員番号を入力し、その番号の社員を foreach で探して Remove(見つからなければ「見つかりませんでした」)
  • 0 終了:ループを抜けて終了

LINQ で機能を増やす(挑戦・各メニューは LINQ 1 行が中心)

  • 4 部署で検索:部署名を入力 → Where(e => e.DepartmentName == 入力) で絞り込んで一覧表示
  • 5 給与の高い順:OrderByDescending(e => e.Salary) で並び替えて一覧表示
  • 6 平均給与:Average(e => e.Salary) で平均を表示
  • (さらに余裕があれば)名前で部分一致検索:Where(e => e.EmployeeName.Contains(キーワード))

各 LINQ メニューは、基本のメニューループに else if を 1 つ足し、その中に 12-3・12-4 で学んだ LINQ を 1 行 書くだけです。

実行例:

=== 社員名簿 ===
[基本] 1: 一覧表示 2: 追加 3: 削除
[LINQ] 4: 部署で検索 5: 給与の高い順 6: 平均給与
0: 終了
> 4
部署名:営業
1002:佐藤昭夫(営業)500000円
1009:星野健一(営業)400000円
> 5
1006:佐々木明子(総務)800000円
1001:山田二郎(総務)500000円
...
> 6
平均給与:540000円
> 0
終了します。

条件・ヒント:

  • メニューの繰り返しは while (true)0break(本文の買い物メモと同じ形)
  • 数値の入力(社員番号・給与)は int.Parse を使う(正しい値が入力される前提でよいint.TryParse で不正入力に備えるのは第 15 章で学びます)
  • 削除は「社員番号で該当する社員を foreach で探して Remove する」方法でよい(List + foreach だけで実装可能)
  • 検索・並び替え・平均(4〜6)は、Where / OrderByDescending / Average を 1 行書いて、結果を foreach で表示するだけ。12-3・12-4 のコードをそのまま真似て構いません(LINQ を実際に使ってみる絶好の機会です)
  • さらに余裕があれば、メニュー判定を if / else if から switch に書き換えてみましょう。switchbreak ではループを抜けられないので、終了の仕方を工夫する必要があります(bool のフラグや return を使う)。制御フローの良い練習になります(任意)

  • プログラムを Visual Studio から実行できる
  • List<int>List<string>List<Employee> を使えている
  • AddCount を使えている
  • Where で絞り込みできている
  • FirstOrDefault で 1 件取得し、null チェックできている
  • OrderBy または OrderByDescending を使えている
  • SumAverage を使えている
  • Select を使えている(発展)
  • ラムダ式の意味を理解している
  • .ToList() で結果を List<T> にできている
  • 課題で使うクラス(Product / Employee など)とサンプルデータ用クラスを別ファイルに書けている
  • インデントが整っている
  • 課題を提出した(Git の commitpush、または講師指示があれば Kadai12 フォルダをサーバへコピーし、提出先フォルダに 提出メモ.txt を作成)

タイマーが鳴ったら、第 12 章の課題プロジェクト群(Kadai12 ソリューション)をその場で Git に提出します。

Terminal window
git status
git add .
git commit -m "Chapter12 タイマー提出: <どこまで完成> / <詰まったポイント>"
git push

実行例:

Terminal window
git commit -m "Chapter12 タイマー提出: 12-1〜12-2完成、12-3は途中 / OrderByDescending と ToList の書き方で詰まった"

Git の詳しい操作は 付録 C、コミットメッセージ形式とサーバコピー提出は本章冒頭「演習課題 > 提出ルール」を参照してください。


この章では、List<T> と LINQ を学習しました。

前半:List<T> だけでできること(まずはここを確実に)

  • List<T> は同じ型のデータを複数管理するコレクション
  • T には intstring だけでなく自作クラスも指定できる
  • AddCountRemove・インデックスアクセスで要素を扱える
  • 配列より追加・削除がしやすい
  • List + foreach だけで、一覧表示・検索・集計(合計 / 人数 / 平均)まで実用的にこなせる(LINQ がなくても十分)

後半:LINQ とラムダ式(基本パターンは第 17 章までに必須・読めて真似て書ければOK)

  • LINQ は、前半の foreach の検索・絞り込み・並び替え・集計を簡潔に書く仕組み
  • LINQ は第 17 章以降の DB・Web 編で当たり前に登場する。第 17 章までに基本パターン(Where/Select/Sum)を真似てでも書けるようにしておくことは必須
  • Where で絞り込み、.ToList()List<T> に変換
  • ラムダ式 e => 条件 は「1 件取り出して条件を判定」と読む
  • FirstOrDefault は最初の 1 件を取得、見つからなければ null
  • OrderBy / OrderByDescending で並び替え
  • Sum / Average で集計
  • Count はプロパティ(全件)とメソッド(条件付き件数)の 2 種類
  • Select で必要な項目だけ取り出せる
  • DB 接続編では、SELECT 結果を List<Employee> で受け、LINQ で扱う

次章では、継承とポリモーフィズム を学習します。

クラス同士の共通点をまとめる 継承、同じ呼び出し方で型に応じた動きをさせる ポリモーフィズム を扱います。 現場の既存コードを読むうえでも重要な考え方で、Employee をベースに Manager(管理職)や PartTimeEmployee(パート社員)などの派生クラスを作る場面で活躍します。