はじめに:「List→Dictionary」は“ただの列に索引をつける作業”
業務コードを書いていると、最初はだいたい List<T> から始まります。
社員一覧、商品一覧、売上明細一覧……どれもまずは「ただのリスト」です。
でも、こう思う瞬間が必ず来ます。
「社員番号から一発で社員を取りたい」
「商品コードからすぐ商品情報を引きたい」
このときに欲しくなるのが Dictionary<TKey, TValue> です。
そして、List<T> から Dictionary<TKey, TValue> に変換するのが、今回の「List→Dictionary」です。
ここでは、LINQ の ToDictionary を使って、
- List から Dictionary に変換する基本
- キーだけ指定するパターン/キーと値を両方指定するパターン
- 重複キー・null への注意
- 実務でよく使うユーティリティ化のコツ
を、初心者向けにかみ砕いて説明していきます。
List と Dictionary の役割の違いをイメージする
List は「順番でアクセス」、Dictionary は「キーでアクセス」
まず、頭の中で役割を整理しておきましょう。
List<T> は「0,1,2,…というインデックスでアクセスするための入れ物」です。list[0], list[1] のように、順番が大事なときに向いています。
Dictionary<TKey, TValue> は「キーから一発で値を引くための入れ物」です。dict[key] で、キーさえ分かればすぐに値にたどり着けます。
業務では、
- 社員番号 → 社員
- 商品コード → 商品
- 部署コード → 部署名
のような「キーから引きたい」場面が多いので、List<T> を Dictionary<TKey, TValue> に変換しておくと、後の処理がとても楽になります。
基本形:List<T> を ToDictionary で変換する
まずはシンプルな数値の例
一番シンプルな例として、List<int> を「値そのものをキーにした Dictionary」にしてみます。
using System;
using System.Collections.Generic;
using System.Linq;
var numbers = new List<int> { 10, 20, 30 };
var dict = numbers.ToDictionary(x => x); // キーも値も x
Console.WriteLine(dict[10]); // 10
Console.WriteLine(dict[20]); // 20
C#ToDictionary(x => x) は、「要素そのものをキーにして辞書を作る」という意味です。
この場合、Dictionary<int, int> になります。
ここでの重要ポイントは、「ToDictionary の第1引数は“キーをどう取り出すか”を決める関数」だということです。x => x と書けば「そのまま」、x => x.Id と書けば「Id プロパティをキーにする」という意味になります。
List<Employee> を「社員番号→社員」の Dictionary にする
社員クラスと List<Employee>
業務で一番よくあるのは、「ID 付きのオブジェクトのリスト」です。
public class Employee
{
public int No { get; set; }
public string Name { get; set; } = "";
public string Department { get; set; } = "";
}
C#var employees = new List<Employee>
{
new Employee { No = 1, Name = "Sato", Department = "Sales" },
new Employee { No = 2, Name = "Suzuki", Department = "Dev" },
new Employee { No = 3, Name = "Tanaka", Department = "Sales" },
};
C#ToDictionary(e => e.No) で「No→Employee」に変換
社員番号をキーにした辞書に変換します。
var employeeMap = employees.ToDictionary(e => e.No);
C#型は Dictionary<int, Employee> になります。
使い方はこうです。
var e2 = employeeMap[2];
Console.WriteLine(e2.Name); // Suzuki
C#ここでの重要ポイントは、「ToDictionary(e => e.No) で“社員番号をキーに、Employee を値にした辞書”ができる」ことです。
値は元の要素そのもの(Employee)なので、そこから名前でも部署でも自由に参照できます。
キーと値を両方指定して List→Dictionary する
「コード→名称」だけ欲しいパターン
ときどき、「オブジェクト全部はいらない、コードと名称だけでいい」という場面があります。
部署クラスを用意します。
public class Department
{
public string Code { get; set; } = ""; // "SLS", "DEV" など
public string Name { get; set; } = ""; // "営業部", "開発部" など
}
C#var departments = new List<Department>
{
new Department { Code = "SLS", Name = "営業部" },
new Department { Code = "DEV", Name = "開発部" },
};
C#コード → 名称の辞書に変換します。
var deptNameMap = departments
.ToDictionary(d => d.Code, d => d.Name);
C#型は Dictionary<string, string> です。
使い方はこうなります。
Console.WriteLine(deptNameMap["SLS"]); // 営業部
C#ここでの重要ポイントは、「ToDictionary(keySelector, valueSelector) で“キーも値も自由に選べる”」ことです。
第1引数がキー、第2引数が辞書に格納される値です。
重複キーで落ちないようにする考え方
ToDictionary はキー重複で例外になる
ToDictionary は、「キーが重複している」と ArgumentException を投げます。
var list = new[] { "A", "B", "A" };
// これは例外
var dict = list.ToDictionary(x => x);
C#業務データでも、「同じ社員番号が二重に入っていた」「同じコードが重複していた」は普通に起こり得ます。
ここでの重要ポイントは、「List→Dictionary する前に、“このキーは一意であるべきか?”を必ず考える」ことです。
一意であるべきなら、例外はむしろ“データ不正の検知”として歓迎できます。
1キーに複数行ぶら下げたいときは GroupBy を挟む
「顧客ごとに複数の売上明細がある」のように、
1つのキーに複数行がぶら下がるのが普通なケースもあります。
var sales = new[]
{
new { Customer = "A", Amount = 1000 },
new { Customer = "A", Amount = 2000 },
new { Customer = "B", Amount = 1500 },
};
C#この場合は、いきなり ToDictionary ではなく、GroupBy を挟みます。
var salesByCustomer = sales
.GroupBy(x => x.Customer)
.ToDictionary(
g => g.Key,
g => g.ToList()); // 1顧客に複数明細
C#型は Dictionary<string, List<匿名型>> になります。
使い方はこうです。
foreach (var s in salesByCustomer["A"])
{
Console.WriteLine(s.Amount); // 1000, 2000
}
C#ここでの重要ポイントは、「“1キーに1件”なら ToDictionary、“1キーに複数件”なら GroupBy→ToDictionary」というパターンを使い分けることです。
null やキー欠損を安全に扱う
null かもしれない List から辞書を作る
List<Employee>? employees = GetEmployeesOrNull();
C#この状態で employees.ToDictionary(...) と書くと、null のときに例外になります。
安全に書くなら、こうです。
var employeeMap = (employees ?? Enumerable.Empty<Employee>())
.ToDictionary(e => e.No);
C#employees が null なら空列として扱い、そのまま空の辞書ができます。
ここでの重要ポイントは、「“辞書は必ず存在する(中身が空のことはある)”状態にしておくと、後続のコードがシンプルになる」ことです。
辞書からの取得は TryGetValue を基本にする
存在しないキーを dict[key] で参照すると、KeyNotFoundException になります。
var e999 = employeeMap[999]; // 例外
C#安全に書くなら TryGetValue を使います。
if (employeeMap.TryGetValue(2, out var emp))
{
Console.WriteLine(emp.Name);
}
else
{
Console.WriteLine("社員が見つかりません");
}
C#ここでの重要ポイントは、「“必ずある前提”なら dict[key]、“ないかもしれない前提”なら TryGetValue」と決めておくことです。
業務的に「存在しないのはあり得ない」なら、あえて例外にしておくのも一つの設計です。
実務で便利な List→Dictionary ユーティリティ
null を吸収する SafeToDictionary
毎回 ?? Enumerable.Empty<T>() と書くのが面倒なら、拡張メソッドにしてしまえます。
public static class DictionaryExtensions
{
public static Dictionary<TKey, TValue> SafeToDictionary<TSource, TKey, TValue>(
this IEnumerable<TSource>? source,
Func<TSource, TKey> keySelector,
Func<TSource, TValue> valueSelector)
where TKey : notnull
{
if (source is null)
{
return new Dictionary<TKey, TValue>();
}
return source.ToDictionary(keySelector, valueSelector);
}
}
C#使い方はこうです。
List<Department>? departments = GetDepartmentsOrNull();
var deptNameMap = departments
.SafeToDictionary(d => d.Code, d => d.Name);
C#ここでの重要ポイントは、「呼び出し側から null チェックを追い出して、“常に Dictionary が返る”契約にしている」ことです。
これで、後続のコードは「辞書は必ずある」という前提で書けます。
実務で意識してほしい設計のポイント
「何をキーにするか」を日本語で言えるようにしてから書く
コードを書く前に、まず言葉で整理してみてください。
社員番号から社員を引きたい
部署コードから部署名を引きたい
顧客コードからその顧客の全明細を引きたい
これが言えれば、ToDictionary の形はほぼ自動的に決まります。
社員番号 → 社員employees.ToDictionary(e => e.No)
部署コード → 部署名departments.ToDictionary(d => d.Code, d => d.Name)
顧客コード → 明細一覧sales.GroupBy(x => x.Customer).ToDictionary(g => g.Key, g => g.ToList())
という対応です。
「一意か」「複数か」を最初に決める
List→Dictionary で一番事故りやすいのが「キー重複」です。
なので、最初にこう決めておくと安全です。
このキーは、業務上、一意であるべきか?
それとも、同じキーに複数行あるのが普通か?
一意なら ToDictionary でよく、
複数なら GroupBy → ToDictionary を選ぶ、というルールを自分の中に持っておくと、迷わなくなります。
まとめ:「List→Dictionary」は“業務データにインデックスを貼る基本テクニック”
List→Dictionary の本質は、
「順番で並んだデータを、“キーで一瞬で引ける形”に組み替える」
ことです。
押さえておきたいポイントは、
ToDictionary(keySelector) で「キー → 元オブジェクト」ToDictionary(keySelector, valueSelector) で「キー → 任意の値」
キー重複があると例外になるので、一意かどうかを最初に考える
1キーに複数行なら GroupBy → ToDictionary
null やキー欠損は ?? Enumerable.Empty<T>() や TryGetValue、SafeToDictionary で安全に扱う
ここまで腹落ちしていれば、
「毎回 List を線形検索して探す」コードから卒業して、
“業務・実務で通用する、キーアクセス前提のデータ構造”を自然に選べるようになります。
