C# Tips | コレクション・LINQ:辞書変換

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

はじめに:「辞書変換」は“検索しやすい形に並び替える技”

業務でよくあるのが、

社員番号から社員情報をすぐ取りたい
商品コードから商品情報を一発で引きたい
部署コードから部署名をすぐ知りたい

こういう「キーから素早く引きたい」場面です。
このときに力を発揮するのが Dictionary<TKey, TValue>、そして LINQ の ToDictionary です。

ここでは、初心者向けに

キーと値の基本
ToDictionary の使い方
重複キー・null への注意
実務でよく使う辞書変換ユーティリティ

をかみ砕いて説明していきます。


基本:List を Dictionary に変換するイメージ

「列」から「キーで引けるマップ」へ

List<T> は「順番に並んだデータの集まり」です。
一方 Dictionary<TKey, TValue> は、「キー → 値」を素早く引ける“マップ”です。

イメージとしては、

社員の一覧(List<Employee>)
 ↓
社員番号 → 社員情報(Dictionary<int, Employee>)

という変換をする感じです。

LINQ の ToDictionary は、この変換を一行で書くためのメソッドです。


ToDictionary の基本形

いちばんシンプルな形

まずは数値のリストを、「値そのものをキーにした辞書」にしてみます。

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) は“要素そのものをキーにする”」という形だということです。
実務では、これより「プロパティをキーにする」パターンをよく使います。


オブジェクトを「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#

社員番号をキーにした辞書に変換します。

var employeeMap = employees.ToDictionary(e => e.No);
C#

これで、こう書けます。

var e2 = employeeMap[2];
Console.WriteLine(e2.Name); // Suzuki
C#

ここでの重要ポイントは、「ToDictionary(e => e.No) で“No をキーに、Employee を値にした辞書”ができる」ことです。
値は元の要素そのもの(Employee)になります。


キーと値の両方を指定する ToDictionary

「コード → 名称」の辞書を作る

「部署コード → 部署名」のように、
値として“全部のオブジェクト”ではなく“特定のプロパティだけ”を持たせたいことも多いです。

部署クラスを用意します。

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#

使い方はこうです。

Console.WriteLine(deptNameMap["SLS"]); // 営業部
C#

ここでの重要ポイントは、「ToDictionary(keySelector, valueSelector) で“キーも値も自由に選べる”」ことです。
keySelector がキー、valueSelector が辞書に格納される値です。


重複キーに注意する

同じキーがあると例外になる

ToDictionary は、「キーが重複している」と例外になります。

var list = new[] { "A", "B", "A" };

// これは例外(ArgumentException)
var dict = list.ToDictionary(x => x);
C#

業務データでも、「同じ社員番号が二重に入っていた」「同じコードが重複していた」などは普通に起こり得ます。

ここでの重要ポイントは、「辞書に変換する前に“キーが一意かどうか”を意識する」ことです。

重複があり得るときのパターン

重複を許容したい場合は、いきなり ToDictionary ではなく、
「キーごとにグルーピングしてから辞書にする」という手があります。

var sales = new[]
{
    new { Customer = "A", Amount = 1000 },
    new { Customer = "A", Amount = 2000 },
    new { Customer = "B", Amount = 1500 },
};

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・キー欠損との付き合い方

キーが存在しないときの扱い

辞書から値を取り出すとき、存在しないキーを dict[key] で参照すると例外になります。

var e999 = employeeMap[999]; // KeyNotFoundException
C#

安全に書くなら TryGetValue を使います。

if (employeeMap.TryGetValue(2, out var emp))
{
    Console.WriteLine(emp.Name);
}
else
{
    Console.WriteLine("社員が見つかりません");
}
C#

ここでの重要ポイントは、「辞書からの取得は“必ずある前提”か、“ないかもしれない前提”かを決める」ことです。
ない可能性があるなら、TryGetValue を使う癖をつけておくと安全です。

null かもしれないコレクションから辞書を作る

List<Employee>? employees = GetEmployeesOrNull();
C#

この場合、いきなり employees.ToDictionary(...) と書くと NullReferenceException です。

var employeeMap = (employees ?? Enumerable.Empty<Employee>())
    .ToDictionary(e => e.No);
C#

employees が null なら空列として扱い、そのまま空の辞書ができます。


実務でよく使う辞書変換ユーティリティ

安全な ToDictionary ラッパーを作る

「null でも空でも、とりあえず辞書にしたい」という場面は多いので、
拡張メソッドにしてしまうのも手です。

public static class DictionaryExtensions
{
    public static Dictionary<TKey, TValue> ToSafeDictionary<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
    .ToSafeDictionary(d => d.Code, d => d.Name);
C#

ここでの重要ポイントは、「呼び出し側から null チェックを消して、“常に辞書が返る”契約にしている」ことです。
これで、後続のコードは「辞書は必ず存在する(中身が空のことはある)」前提で書けます。


実務で意識してほしいこと

「何をキーにするか」を日本語で先に決める

辞書変換を書く前に、必ず言葉で整理してください。

社員番号から社員を引きたい
部署コードから部署名を引きたい
顧客コードからその顧客の全明細を引きたい

これが決まれば、ToDictionary の形はほぼ自動的に決まります。

社員番号 → 社員
ToDictionary(e => e.No)

部署コード → 部署名
ToDictionary(d => d.Code, d => d.Name)

顧客コード → 明細一覧
GroupBy(x => x.Customer).ToDictionary(g => g.Key, g => g.ToList())

という対応です。

「一意か」「複数か」を最初に決める

辞書変換で一番ハマりやすいのが「キー重複」です。
なので、最初にこう決めておくと安全です。

このキーは、必ず一意であるべきか?
同じキーに複数行あるのが普通か?

一意なら ToDictionary
複数なら GroupBy → ToDictionary というパターンを使い分けましょう。


まとめ:「辞書変換ユーティリティ」は“業務データにインデックスを貼る道具”

辞書変換の本質は、

「順番で並んだデータを、“キーで一瞬で引ける形”に組み替える」

ことです。

押さえておきたいポイントは、

ToDictionary(keySelector) で「キー → 元オブジェクト」
ToDictionary(keySelector, valueSelector) で「キー → 任意の値」
キー重複があると例外になるので、一意かどうかを意識する
複数行をぶら下げたいときは GroupBy → ToDictionary
null やキー欠損は ?? Enumerable.Empty<T>()TryGetValue で安全に扱う

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

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