リストを「検索しやすい形」に変換する
C#でデータを扱う際、データベースやAPIから取得したデータは、多くの場合List<T>や配列として提供されます。これをそのまま「IDを使って特定のデータを検索」しようとすると、リストの先頭から順に探す(線形探索)必要があり、データ量が増えると処理速度が低下します。
特定のキー(IDなど)に基づいて頻繁にデータへアクセスする場合は、リストを**Dictionary<TKey, TValue>(辞書)**に変換するのが定石です。辞書に変換することで、データの検索速度はO(1)(ほぼ一瞬)になります。
LINQのToDictionaryメソッドを使用すると、コレクションからキーと値を抽出して、簡単にDictionaryを作成できます。
この記事では、ToDictionaryの2つの主要な使い方と、使用時に発生しやすい例外への対策について解説します。
1. キーのみを指定して変換する
ToDictionaryの最も基本的な使い方は、「どのプロパティを辞書のキーにするか」だけを指定する方法です。この場合、値(Value)にはオブジェクトそのものが格納されます。
- 戻り値の型:
Dictionary<キーの型, オブジェクトの型>
コード例:IDをキーにして辞書化
都道府県コード(ID)を持つクラスのリストを、IDで検索できる辞書に変換する例です。
using System;
using System.Collections.Generic;
using System.Linq;
// 都道府県データを表すレコード
public record Prefecture(int Id, string Name, string Region);
public class ToDictionaryBasicExample
{
public static void Main()
{
// データソース(配列)
var prefectureList = new[]
{
new Prefecture(1, "Hokkaido", "Hokkaido"),
new Prefecture(13, "Tokyo", "Kanto"),
new Prefecture(23, "Aichi", "Chubu"),
new Prefecture(27, "Osaka", "Kansai"),
new Prefecture(40, "Fukuoka", "Kyushu")
};
// ID をキーにして Dictionary に変換
// キー: int (Id), 値: Prefecture (オブジェクト自体)
Dictionary<int, Prefecture> prefectureDict =
prefectureList.ToDictionary(p => p.Id);
Console.WriteLine("--- 辞書変換後のアクセス ---");
// IDを指定して高速にアクセス可能
int searchId = 13;
if (prefectureDict.TryGetValue(searchId, out var pref))
{
Console.WriteLine($"ID: {searchId} => {pref.Name} ({pref.Region})");
}
// 辞書の中身を表示
Console.WriteLine("\n--- 全データ ---");
foreach (KeyValuePair<int, Prefecture> item in prefectureDict)
{
Console.WriteLine($"Key: {item.Key}, Value: {item.Value.Name}");
}
}
}
出力結果:
--- 辞書変換後のアクセス ---
ID: 13 => Tokyo (Kanto)
--- 全データ ---
Key: 1, Value: Hokkaido
Key: 13, Value: Tokyo
Key: 23, Value: Aichi
Key: 27, Value: Osaka
Key: 40, Value: Fukuoka
2. キーと値の両方を指定して変換する
キーだけでなく、「値として何を格納するか」も指定したい場合があります。オブジェクト全体ではなく、特定のプロパティ(例:名前だけ)を値として保持したい場合に使用します。
- 戻り値の型:
Dictionary<キーの型, 指定した値の型>
コード例:IDをキー、名前を値にする
先ほどのデータから、「ID」と「都道府県名」だけのペアを持つ辞書を作成します。
using System;
using System.Collections.Generic;
using System.Linq;
public class ToDictionaryValueSelectorExample
{
public static void Main()
{
var prefectureList = new[]
{
new { Id = 1, Name = "Hokkaido", Code = "HKD" },
new { Id = 13, Name = "Tokyo", Code = "TKY" },
new { Id = 27, Name = "Osaka", Code = "OSK" }
};
// 第1引数: キーの選択 (Code)
// 第2引数: 値の選択 (Name)
Dictionary<string, string> codeToNameMap =
prefectureList.ToDictionary(x => x.Code, x => x.Name);
Console.WriteLine("--- コード検索マップ ---");
foreach (var pair in codeToNameMap)
{
Console.WriteLine($"{pair.Key} -> {pair.Value}");
}
}
}
出力結果:
--- コード検索マップ ---
HKD -> Hokkaido
TKY -> Tokyo
OSK -> Osaka
重要な注意点:キーの重複は例外発生
ToDictionaryを使用する上で最も注意すべき点は、キーとして指定した値が重複していた場合、ArgumentException が発生して処理が停止することです。Dictionaryは一意なキーしか持てないためです。
もしデータソースに重複が含まれる可能性がある場合は、事前に重複を除去するか、ToLookupメソッド(キーに対して複数の値を持てる)の使用を検討する必要があります。
重複回避のアプローチ
var duplicateData = new[]
{
new { Id = 1, Name = "A" },
new { Id = 1, Name = "B" } // IDが重複
};
// そのまま ToDictionary すると例外発生
// var dict = duplicateData.ToDictionary(x => x.Id); // ArgumentException!
// 対策: GroupBy などで重複を排除してから辞書化する
// 例: 重複した場合は「最後のデータ」を採用する
var safeDict = duplicateData
.GroupBy(x => x.Id)
.ToDictionary(g => g.Key, g => g.Last());
Console.WriteLine(safeDict[1].Name); // "B"
まとめ
ToDictionaryは、シーケンスを「検索に最適化された形式」に変換する強力なメソッドです。
ToDictionary(x => x.Key): キーを指定し、要素そのものを値とする辞書を作成します。ToDictionary(x => x.Key, x => x.Value): キーと、抽出した値を格納する辞書を作成します。- 注意: キーの重複は許されません。データの一意性が保証されていない場合は、事前の重複排除処理が必要です。
