C# Tips | コレクション・LINQ:List→Dictionary

C# C#
スポンサーリンク

はじめに:「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>()TryGetValueSafeToDictionary で安全に扱う

ここまで腹落ちしていれば、
「毎回 List を線形検索して探す」コードから卒業して、
“業務・実務で通用する、キーアクセス前提のデータ構造”を自然に選べるようになります。

タイトルとURLをコピーしました