C#のOrderedDictionaryとは?順序を保持するDictionaryの使い方・違い・代替方法を徹底解説
はじめに
C#でキーと値を扱うコレクションといえば、まず思い浮かぶのはDictionary<TKey,TValue>です。キーを指定して高速に値を取得できるため、設定値、マスターデータ、キャッシュ、集計結果など、さまざまな場面で使われます。
一方で、Dictionaryを使っていると次のような疑問が出てくることがあります。
「追加した順番のままループしたい」
「キーでも取り出したいが、表示順も維持したい」
「C#に順序を保持するDictionaryはあるのか」
「c# ordered dictionaryは通常のDictionaryと何が違うのか」
このような場面で候補になるのがOrderedDictionaryです。OrderedDictionaryは、キーと値のペアを管理しながら、要素の順序も扱えるコレクションです。従来のSystem.Collections.Specialized.OrderedDictionaryは非ジェネリック型でしたが、.NET 9ではジェネリック版のOrderedDictionary<TKey,TValue>も導入されています。Microsoft公式ドキュメントでも、従来のOrderedDictionaryはキーまたはインデックスでアクセスできるキー・値ペアのコレクションと説明され、.NET 9では型安全なジェネリック版が追加されたことが示されています。
この記事では、C#のOrderedDictionaryについて、基本的な使い方、通常のDictionaryとの違い、SortedDictionaryやSortedListとの比較、.NET 9以降のジェネリック版、代替方法、実践的な使いどころまで詳しく解説します。
1. C#のOrderedDictionaryとは?順序を保持できるDictionaryの基本
OrderedDictionaryは、キーと値をペアで保持しながら、要素の順序も扱えるコレクションです。
通常のDictionary<TKey,TValue>は「キーから値を高速に取得する」ことを主な目的としています。一方、OrderedDictionaryは「キーで値を取得する」だけでなく、「何番目の要素か」というインデックスでも要素にアクセスできます。
つまり、Dictionaryのようなキー検索と、Listのような順序管理の両方を必要とする場面で役立つ型です。
1-1. OrderedDictionaryは「追加順」を保持するキー・値コレクション
OrderedDictionaryは、基本的に要素を追加した順番を保ったまま管理できます。
たとえば、次のように要素を追加したとします。
C#OrderedDictionary items = new OrderedDictionary();
items.Add("first", "1番目");
items.Add("second", "2番目");
items.Add("third", "3番目");
この場合、foreachで列挙すると、追加した順番に近い形で処理できます。表示順や出力順をそのまま保持したい場合に便利です。
たとえば、フォーム項目、メニュー項目、設定ファイルの出力順、CSVの列順などでは、「キーで取り出せること」と「順番が維持されること」の両方が重要になることがあります。そのようなケースでOrderedDictionaryは有力な選択肢になります。
1-2. 通常のDictionaryとの最大の違いは順序を前提に扱えること
Dictionary<TKey,TValue>との大きな違いは、順序を前提にした処理ができるかどうかです。
Dictionary<TKey,TValue>は、キーを使って値を取得するためのコレクションです。Microsoft公式ドキュメントでは、Dictionary<TKey,TValue>を列挙したときに項目が返される順序は未定義とされています。つまり、見た目上は追加順に見える環境があっても、その順序に依存するコードを書くべきではありません。
一方、OrderedDictionaryは順序付きのコレクションとして扱えるため、順番が意味を持つ処理に向いています。
たとえば、次のような処理です。
C#foreach (DictionaryEntry entry in items)
{
Console.WriteLine($"{entry.Key}: {entry.Value}");
}
このように、登録した順番を意識しながらキーと値を処理できます。
1-3. キーでもインデックスでも要素にアクセスできる
OrderedDictionaryの特徴は、キーとインデックスの両方でアクセスできる点です。
キーでアクセスする場合は、通常のDictionaryに近い感覚で使えます。
C#Console.WriteLine(items["second"]);
インデックスでアクセスする場合は、リストのように順番を指定できます。
C#Console.WriteLine(items[1]);
この例では、1番目、つまり2番目に追加された要素の値を取得します。
従来の非ジェネリック版OrderedDictionaryでは、インデックス指定で取得できるのは値です。キーと値の両方を取り出したい場合は、foreachでDictionaryEntryとして扱うか、KeysやValuesを使います。
1-4. OrderedDictionaryが役立つ代表的な場面
OrderedDictionaryは、単にキーと値を保存するだけでなく、順序にも意味がある場面で役立ちます。
代表的な例は、画面表示の順序を維持したい場合です。たとえば、入力フォームの項目を「氏名」「メールアドレス」「電話番号」「住所」の順に表示したい場合、キーで項目を識別しながら、表示順も保持できます。
また、JSONやCSVの出力順を制御したい場合にも使えます。仕様上、すべてのデータ形式で順序が意味を持つとは限りませんが、人間が読む設定ファイルや帳票データでは、出力順が保たれているほうが扱いやすくなります。
そのほか、メニュー、ステップ処理、履歴、簡易的なキャッシュ、設定項目、帳票の列定義などでも利用できます。
2. C#のDictionaryは順序を保持する?OrderedDictionaryが必要になる理由
OrderedDictionaryを理解するうえで重要なのが、通常のDictionary<TKey,TValue>の順序に対する考え方です。
C#のDictionaryは非常に便利ですが、順序を保証するためのコレクションではありません。キー検索を主目的とするため、順序が重要な処理では注意が必要です。
2-1. Dictionaryの列挙順に依存してよいのか
結論から言うと、Dictionary<TKey,TValue>の列挙順に依存するコードは避けるべきです。
一部の.NET環境では、Dictionaryをforeachしたときに追加順で出力されているように見えることがあります。しかし、公式ドキュメントでは、Dictionary<TKey,TValue>の項目が返される順序は未定義とされています。
そのため、次のようなコードで「必ず追加順に出力される」と考えるのは危険です。
C#Dictionary<string, string> dict = new Dictionary<string, string>();
dict.Add("first", "1番目");
dict.Add("second", "2番目");
dict.Add("third", "3番目");
foreach (var item in dict)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
実行結果が期待どおりに見えても、それは順序保証を意味しません。
2-2. 「たまたま追加順に見える」と「仕様として保証される」の違い
プログラムでは、「現在の環境でそう動いた」と「仕様として保証されている」は明確に区別する必要があります。
Dictionaryの列挙結果が追加順に見える場合でも、将来の.NETバージョン、実行環境、データ件数、削除や再追加の有無によって挙動が変わる可能性があります。
順序が処理結果に影響しない場合は、Dictionaryで問題ありません。しかし、順序が画面表示、ファイル出力、テスト結果、業務ロジックに影響する場合は、順序を保証できるコレクションを選ぶべきです。
2-3. 順序が重要な処理でDictionaryを使うリスク
順序が重要な処理でDictionaryを使うと、次のような問題が起きる可能性があります。
たとえば、CSV出力で列順が変わると、読み込む側のシステムが正しく処理できないことがあります。画面表示の順序が変わると、ユーザーにとって不自然な並びになることもあります。単体テストで期待する順序と実際の順序がずれると、ロジック自体は正しくてもテストが不安定になることがあります。
このような問題は、件数が少ないうちは気づきにくいものです。開発環境では動いていたのに、本番環境や別バージョンのランタイムで問題が表面化することもあります。
2-4. OrderedDictionaryを選ぶべきケース
OrderedDictionaryを選ぶべきなのは、キー検索と順序保持の両方が必要なケースです。
たとえば、次のような条件に当てはまる場合はOrderedDictionaryが向いています。
キーで値を取得したい。追加した順番でループしたい。インデックスを使って何番目の要素かを扱いたい。表示順や出力順を安定させたい。キーの重複は避けたい。
逆に、順序が不要でキー検索だけできればよい場合は、通常のDictionary<TKey,TValue>のほうがシンプルです。キー順に並べたい場合はSortedDictionary<TKey,TValue>やSortedList<TKey,TValue>を検討します。
3. OrderedDictionaryの基本的な使い方
ここからは、従来から使える非ジェネリック版のSystem.Collections.Specialized.OrderedDictionaryを中心に、基本的な使い方を見ていきます。
非ジェネリック版は、キーも値もobjectとして扱います。そのため、値を特定の型として使う場合はキャストが必要になることがあります。
3-1. usingと名前空間
従来のOrderedDictionaryを使うには、次の名前空間を指定します。
C#using System.Collections;
using System.Collections.Specialized;
OrderedDictionary本体はSystem.Collections.Specialized名前空間にあります。foreachでDictionaryEntryを使う場合は、System.Collectionsも必要です。
C#using System;
using System.Collections;
using System.Collections.Specialized;
class Program
{
static void Main()
{
OrderedDictionary dict = new OrderedDictionary();
}
}
3-2. OrderedDictionaryの作成方法
OrderedDictionaryは、次のように作成します。
C#OrderedDictionary dict = new OrderedDictionary();
初期容量を指定することもできます。
C#OrderedDictionary dict = new OrderedDictionary(10);
キーの比較方法を指定したい場合は、IEqualityComparerを渡すコンストラクターも利用できます。たとえば、文字列キーの大文字小文字を区別しないようにしたい場合などです。
C#OrderedDictionary dict = new OrderedDictionary(StringComparer.OrdinalIgnoreCase);
この場合、"Name"と"name"は同じキーとして扱われます。
3-3. Addで要素を追加する
要素を追加するにはAddメソッドを使います。
C#OrderedDictionary dict = new OrderedDictionary();
dict.Add("id", 1);
dict.Add("name", "田中");
dict.Add("email", "tanaka@example.com");
Addでは、第1引数にキー、第2引数に値を指定します。
OrderedDictionaryでは同じキーを重複して追加することはできません。すでに存在するキーをAddしようとすると例外が発生します。値を更新したい場合は、インデクサーを使います。
C#dict["name"] = "佐藤";
3-4. キーを指定して値を取得・更新する
キーを指定して値を取得するには、次のように書きます。
C#object name = dict["name"];
Console.WriteLine(name);
値を文字列として使いたい場合は、キャストします。
C#string name = (string)dict["name"];
値を更新する場合も、キーを指定して代入します。
C#dict["email"] = "sato@example.com";
この操作では値が更新されるだけで、要素の順序は変わりません。
3-5. インデックスを指定して要素を取得する
OrderedDictionaryでは、インデックスを指定して値を取得できます。
C#object firstValue = dict[0];
Console.WriteLine(firstValue);
たとえば、先ほどの例では0番目が"id"の値、1番目が"name"の値、2番目が"email"の値になります。
C#Console.WriteLine(dict[0]); // 1
Console.WriteLine(dict[1]); // 佐藤
Console.WriteLine(dict[2]); // sato@example.com
インデックス指定は順序付きコレクションならではの機能です。通常のDictionary<TKey,TValue>には、このようなインデックスによる位置アクセスはありません。
3-6. foreachで追加順にループする
OrderedDictionaryをforeachで処理する場合、各要素はDictionaryEntryとして取り出します。公式ドキュメントでも、非ジェネリック版OrderedDictionaryの各要素はDictionaryEntryであり、foreachではDictionaryEntryとして扱う構文が示されています。
C#foreach (DictionaryEntry entry in dict)
{
Console.WriteLine($"{entry.Key}: {entry.Value}");
}
出力例は次のようになります。
id: 1
name: 佐藤
email: sato@example.com
DictionaryEntryにはKeyとValueプロパティがあります。
3-7. Remove・RemoveAt・Clearで要素を削除する
キーを指定して削除するにはRemoveを使います。
C#dict.Remove("email");
インデックスを指定して削除するにはRemoveAtを使います。
C#dict.RemoveAt(0);
すべての要素を削除するにはClearを使います。
C#dict.Clear();
RemoveAtを使うと、指定した位置の要素が削除され、後ろの要素のインデックスが詰められます。
3-8. Containsでキーの存在を確認する
キーが存在するかどうかを確認するにはContainsを使います。
C#if (dict.Contains("name"))
{
Console.WriteLine(dict["name"]);
}
存在しないキーにアクセスすると意図しない結果や例外につながることがあるため、必要に応じて事前に確認すると安全です。
非ジェネリック版のOrderedDictionaryでは、ContainsKeyではなくContainsを使う点に注意しましょう。
4. OrderedDictionaryのサンプルコードで動作を理解する
ここでは、実際のサンプルコードを使ってOrderedDictionaryの動作を確認します。
4-1. 追加した順番どおりに出力するサンプル
C#using System;
using System.Collections;
using System.Collections.Specialized;
class Program
{
static void Main()
{
OrderedDictionary fruits = new OrderedDictionary();
fruits.Add("apple", "りんご");
fruits.Add("banana", "バナナ");
fruits.Add("orange", "オレンジ");
foreach (DictionaryEntry entry in fruits)
{
Console.WriteLine($"{entry.Key}: {entry.Value}");
}
}
}
出力例です。
apple: りんご
banana: バナナ
orange: オレンジ
このように、追加した順番を保って処理できます。
4-2. キーで値を取り出すサンプル
C#OrderedDictionary user = new OrderedDictionary();
user.Add("id", 1001);
user.Add("name", "田中");
user.Add("role", "Admin");
Console.WriteLine(user["name"]);
出力例です。
田中
キーを指定して値を取り出せるため、通常のDictionaryに近い使い方ができます。
4-3. インデックスで値を取り出すサンプル
C#OrderedDictionary user = new OrderedDictionary();
user.Add("id", 1001);
user.Add("name", "田中");
user.Add("role", "Admin");
Console.WriteLine(user[0]);
Console.WriteLine(user[1]);
Console.WriteLine(user[2]);
出力例です。
1001
田中
Admin
このように、追加順に基づく位置を指定して値を取り出せます。
4-4. 値を更新しても順序が変わらないことを確認する
C#OrderedDictionary settings = new OrderedDictionary();
settings.Add("theme", "light");
settings.Add("language", "ja");
settings.Add("fontSize", 14);
settings["language"] = "en";
foreach (DictionaryEntry entry in settings)
{
Console.WriteLine($"{entry.Key}: {entry.Value}");
}
出力例です。
theme: light
language: en
fontSize: 14
languageの値は更新されていますが、順序は変わっていません。
値の更新は、要素を削除して再追加する処理とは異なります。順序を変えずに値だけ変更したい場合は、キーを指定して代入する方法を使います。
4-5. 削除後に再追加した場合の順序を確認する
C#OrderedDictionary steps = new OrderedDictionary();
steps.Add("step1", "入力");
steps.Add("step2", "確認");
steps.Add("step3", "完了");
steps.Remove("step2");
steps.Add("step2", "再確認");
foreach (DictionaryEntry entry in steps)
{
Console.WriteLine($"{entry.Key}: {entry.Value}");
}
出力例です。
step1: 入力
step3: 完了
step2: 再確認
削除したキーを再度追加すると、新しい要素として末尾に追加されます。
元の位置に戻したい場合は、Insertを使って指定位置に挿入する方法を検討します。
C#steps.Insert(1, "step2", "再確認");
Insertを使えば、指定したインデックス位置に新しい要素を挿入できます。非ジェネリック版OrderedDictionaryにも、指定した位置にキーと値を挿入するInsertメソッドがあります。
5. OrderedDictionaryとDictionary・SortedDictionary・SortedListの違い
C#にはキーと値を扱うコレクションが複数あります。OrderedDictionaryを適切に使うには、ほかのコレクションとの違いを理解することが重要です。
5-1. Dictionaryとの違い:順序保持の有無
Dictionary<TKey,TValue>は、キーによる高速な検索に向いたコレクションです。
C#Dictionary<string, int> scores = new Dictionary<string, int>();
scores.Add("Alice", 90);
scores.Add("Bob", 80);
scores.Add("Charlie", 85);
キーを指定して値を取り出す処理は得意です。
C#Console.WriteLine(scores["Alice"]);
しかし、列挙順は未定義です。順序が不要な場合はDictionaryで十分ですが、追加順や表示順が重要な場合はOrderedDictionaryを検討します。
5-2. SortedDictionaryとの違い:追加順かキー順か
SortedDictionary<TKey,TValue>は、キーの順序で要素を管理するコレクションです。
たとえば、キーが文字列であれば文字列の比較順、キーが数値であれば数値順に並びます。Microsoft公式ドキュメントでも、SortedDictionary<TKey,TValue>は内部ツリーによってソート順を維持し、列挙時にもそのソート順が維持されると説明されています。
C#SortedDictionary<string, string> dict = new SortedDictionary<string, string>();
dict.Add("c", "C");
dict.Add("a", "A");
dict.Add("b", "B");
foreach (var item in dict)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
出力例です。
a: A
b: B
c: C
OrderedDictionaryは追加順、SortedDictionaryはキー順です。
「登録した順番を維持したい」ならOrderedDictionary、「キーで常に並べ替えたい」ならSortedDictionaryを使います。
5-3. SortedListとの違い:内部構造と向いている用途
SortedList<TKey,TValue>もキー順で要素を管理するコレクションです。
名前にListとありますが、キーと値のペアを扱う点ではDictionary系のコレクションです。SortedDictionary<TKey,TValue>と同じく、追加順ではなくキー順に並びます。
一般的には、要素の追加・削除が頻繁な場合はSortedDictionary、比較的読み取りが多く、インデックスアクセスも使いたい場合はSortedListが候補になります。
ただし、どちらも「追加順を保持する」ためのコレクションではありません。追加順が重要ならOrderedDictionaryを選びます。
5-4. ListとDictionaryを併用する方法との違い
OrderedDictionaryを使わず、ListとDictionaryを組み合わせて順序を管理する方法もあります。
C#List<string> order = new List<string>();
Dictionary<string, string> values = new Dictionary<string, string>();
order.Add("name");
values["name"] = "田中";
order.Add("email");
values["email"] = "tanaka@example.com";
この方法では、Listで順序を管理し、Dictionaryでキー検索を行います。
柔軟性は高いですが、キーの追加、削除、重複チェック、順序変更などを自分で整合させる必要があります。実装を間違えると、ListにはキーがあるのにDictionaryには値がない、またはその逆の状態になる可能性があります。
OrderedDictionaryを使えば、順序とキー検索を1つのコレクションで扱えます。
5-5. LINQのOrderByで並べ替える方法との違い
DictionaryやListのデータを、必要なタイミングでLINQのOrderByを使って並べ替える方法もあります。
C#var ordered = dict.OrderBy(x => x.Key);
foreach (var item in ordered)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
この方法は、「保存時には順序を持たなくてもよいが、出力時だけ並べ替えたい」という場合に便利です。
ただし、OrderByはその時点で並べ替えた結果を作る処理です。コレクション自体が順序を保持しているわけではありません。
常に順序付きで管理したい場合はOrderedDictionary、必要な場面だけ並べ替えたい場合はOrderByが向いています。
5-6. 用途別の選び方早見表
| やりたいこと | 候補 |
|---|---|
| キーで高速に値を取得したい | Dictionary<TKey,TValue> |
| 追加順を保持したい | OrderedDictionary |
| .NET 9以降で型安全に追加順を保持したい | OrderedDictionary<TKey,TValue> |
| キー順に並べたい | SortedDictionary<TKey,TValue> |
| キー順で、読み取りやインデックスアクセスを重視したい | SortedList<TKey,TValue> |
| 一時的に並べ替えて出力したい | LINQのOrderBy |
| 複雑な順序制御を自分で実装したい | DictionaryとListの併用 |
コレクション選びでは、「キー検索が必要か」「順序が必要か」「その順序は追加順かキー順か」を整理すると判断しやすくなります。
6. .NET 9以降のジェネリック版OrderedDictionary<TKey,TValue>
従来のOrderedDictionaryは便利な一方で、非ジェネリック型であることが大きな弱点でした。
.NET 9では、System.Collections.Generic.OrderedDictionary<TKey,TValue>が導入され、型安全に順序付きDictionaryを扱えるようになりました。公式の.NET 9ライブラリ新機能でも、従来のOrderedDictionaryはキーと値がobjectとして扱われる非ジェネリック型だったが、.NET 9で効率的なジェネリック版OrderedDictionary<TKey,TValue>が導入されたと説明されています。
6-1. 従来のOrderedDictionaryは非ジェネリック型
従来のOrderedDictionaryでは、キーも値もobjectとして扱われます。
C#OrderedDictionary dict = new OrderedDictionary();
dict.Add("age", 30);
int age = (int)dict["age"];
このように、値を取り出すときにキャストが必要です。
また、異なる型の値を混在させることもできてしまいます。
C#dict.Add("name", "田中");
dict.Add("age", 30);
dict.Add("isAdmin", true);
柔軟ではありますが、意図しない型の値を入れてしまってもコンパイル時に検出できません。
6-2. ジェネリック版OrderedDictionary<TKey,TValue>の特徴
.NET 9以降では、次のようにジェネリック版を使えます。
C#using System.Collections.Generic;
OrderedDictionary<string, int> scores = new OrderedDictionary<string, int>();
scores.Add("Alice", 90);
scores.Add("Bob", 80);
scores.Add("Charlie", 85);
キーの型はstring、値の型はintとして固定されます。
値を取り出すときもキャストは不要です。
C#int aliceScore = scores["Alice"];
ジェネリック版は、IDictionary<TKey,TValue>だけでなく、IList<KeyValuePair<TKey,TValue>>やIReadOnlyDictionary<TKey,TValue>、IReadOnlyList<KeyValuePair<TKey,TValue>>なども実装しています。公式ドキュメントでも、OrderedDictionary<TKey,TValue>はキーと値の型パラメーターを持ち、キー検索に加えてリスト的なインターフェイスも実装するコレクションとして示されています。
6-3. 型安全に扱えるメリット
ジェネリック版の最大のメリットは、型安全に扱えることです。
たとえば、次のコードはコンパイルエラーになります。
C#OrderedDictionary<string, int> scores = new OrderedDictionary<string, int>();
scores.Add("Alice", 90);
scores.Add("Bob", "80"); // エラー
値の型がintと決まっているため、文字列"80"を誤って追加することはできません。
このように、型のミスをコンパイル時に検出できるため、実行時エラーを減らせます。
6-4. DictionaryEntryやキャストが不要になるメリット
非ジェネリック版では、foreach時にDictionaryEntryを使います。
C#foreach (DictionaryEntry entry in dict)
{
string key = (string)entry.Key;
int value = (int)entry.Value;
}
ジェネリック版では、KeyValuePair<TKey,TValue>として扱えます。
C#foreach (KeyValuePair<string, int> entry in scores)
{
Console.WriteLine($"{entry.Key}: {entry.Value}");
}
また、varを使えばさらに簡潔に書けます。
C#foreach (var entry in scores)
{
Console.WriteLine($"{entry.Key}: {entry.Value}");
}
キャストが不要になるため、コードが読みやすくなり、型変換ミスも減ります。
6-5. .NETのバージョンによる使い分け
.NET 9以降を使える場合は、基本的にジェネリック版OrderedDictionary<TKey,TValue>を優先するとよいでしょう。
理由は、型安全で、DictionaryEntryやキャストが不要で、通常のジェネリックコレクションに近い感覚で扱えるためです。
一方、.NET Frameworkや.NET 8以前を対象にしている場合は、従来のSystem.Collections.Specialized.OrderedDictionaryを使うか、DictionaryとListを併用する方法、または外部ライブラリを検討します。
6-6. 既存コードからジェネリック版へ移行する考え方
既存コードで非ジェネリック版OrderedDictionaryを使っている場合、移行時にはキーと値の型を明確にします。
たとえば、次のようなコードがあるとします。
C#OrderedDictionary oldDict = new OrderedDictionary();
oldDict.Add("apple", 100);
oldDict.Add("banana", 200);
値がすべてintであれば、次のように移行できます。
C#OrderedDictionary<string, int> newDict = new OrderedDictionary<string, int>();
newDict.Add("apple", 100);
newDict.Add("banana", 200);
ただし、値に複数の型が混在している場合は、単純には移行できません。
C#oldDict.Add("name", "田中");
oldDict.Add("age", 30);
oldDict.Add("enabled", true);
この場合は、値の型をobjectにするか、専用のクラスを作ることを検討します。
C#class UserSetting
{
public string Name { get; set; }
public int Age { get; set; }
public bool Enabled { get; set; }
}
型が明確なデータ構造にできるなら、OrderedDictionary<string, object>よりも専用クラスやレコードを使ったほうが保守しやすくなります。
7. OrderedDictionaryを使うときの注意点
OrderedDictionaryは便利ですが、使うときにはいくつか注意点があります。
特に、非ジェネリック版では型の扱い、null、重複キー、パフォーマンスに注意が必要です。
7-1. キーにnullは使えるのか
非ジェネリック版OrderedDictionaryでは、キーにnullを使うことはできません。Microsoft公式ドキュメントでも、OrderedDictionaryの各要素はDictionaryEntryに格納され、キーをnullにすることはできないと説明されています。
次のようなコードは避けるべきです。
C#OrderedDictionary dict = new OrderedDictionary();
dict.Add(null, "value"); // NG
キーには、必ず一意に識別できる値を指定します。
7-2. 値にnullを入れられるのか
非ジェネリック版OrderedDictionaryでは、値にはnullを設定できます。公式ドキュメントでも、キーはnullにできない一方で、値は指定可能とされています。
C#OrderedDictionary dict = new OrderedDictionary();
dict.Add("memo", null);
ただし、値を取り出して使うときにはnullチェックが必要です。
C#if (dict["memo"] != null)
{
Console.WriteLine(dict["memo"].ToString());
}
7-3. 同じキーを追加したときの挙動
OrderedDictionaryでは、同じキーを複数回追加することはできません。
C#OrderedDictionary dict = new OrderedDictionary();
dict.Add("name", "田中");
dict.Add("name", "佐藤"); // 例外
値を変更したい場合は、Addではなくインデクサーで更新します。
C#dict["name"] = "佐藤";
キーが存在するかどうか分からない場合は、Containsで確認してから追加または更新します。
C#if (dict.Contains("name"))
{
dict["name"] = "佐藤";
}
else
{
dict.Add("name", "佐藤");
}
7-4. foreachで取り出せる型に注意する
非ジェネリック版OrderedDictionaryをforeachで回すときは、DictionaryEntryを使います。
C#foreach (DictionaryEntry entry in dict)
{
Console.WriteLine($"{entry.Key}: {entry.Value}");
}
次のようにKeyValuePair<string, string>で受け取ることはできません。
C#foreach (KeyValuePair<string, string> entry in dict) // NG
{
}
Dictionary<TKey,TValue>に慣れていると間違えやすい点です。
7-5. 非ジェネリック版ではキャストが必要になる
非ジェネリック版では、値を取り出したときの型はobjectです。
C#object value = dict["age"];
intとして使う場合はキャストが必要です。
C#int age = (int)dict["age"];
キャストに失敗すると実行時例外が発生します。
C#string ageText = (string)dict["age"]; // 実際の値がintなら例外
型安全に扱いたい場合は、.NET 9以降であればジェネリック版OrderedDictionary<TKey,TValue>を検討しましょう。
7-6. パフォーマンス面でDictionaryより不利になる場合がある
OrderedDictionaryは順序を管理するため、単純なキー検索だけを目的にするならDictionary<TKey,TValue>よりも余分なコストがかかる場合があります。
特に、要素数が多く、順序が不要で、キー検索だけを大量に行う場合は、通常のDictionary<TKey,TValue>を使うほうが適しています。
一方で、ジェネリック版OrderedDictionary<TKey,TValue>については、公式ドキュメントで、キーによる検索はDictionary<TKey,TValue>に近い複雑さを持ち、それ以外の操作はList<T>に近い複雑さを持つと説明されています。
つまり、OrderedDictionaryは「順序も必要」という要件があるときに選ぶべきコレクションです。順序が不要ならDictionary、キー順が必要ならSortedDictionary、追加順が必要ならOrderedDictionaryというように使い分けます。
8. OrderedDictionaryの代替方法
OrderedDictionaryは便利ですが、すべてのケースで最適とは限りません。要件によっては、別の方法のほうがシンプルになることもあります。
8-1. DictionaryとListを組み合わせて順序を管理する
もっとも代表的な代替方法は、DictionaryとListを組み合わせる方法です。
C#Dictionary<string, string> values = new Dictionary<string, string>();
List<string> order = new List<string>();
void Add(string key, string value)
{
if (!values.ContainsKey(key))
{
order.Add(key);
}
values[key] = value;
}
出力するときは、Listに保存したキーの順番でDictionaryから値を取得します。
C#foreach (string key in order)
{
Console.WriteLine($"{key}: {values[key]}");
}
この方法は柔軟ですが、削除や並べ替えを行う場合に整合性管理が必要です。
C#values.Remove("name");
order.Remove("name");
どちらか一方だけを更新してしまうと、不整合が発生します。
8-2. List<KeyValuePair<TKey,TValue>>を使う
単純に順序付きのキー・値ペアを保持したいだけなら、List<KeyValuePair<TKey,TValue>>を使う方法もあります。
C#List<KeyValuePair<string, string>> items = new List<KeyValuePair<string, string>>();
items.Add(new KeyValuePair<string, string>("name", "田中"));
items.Add(new KeyValuePair<string, string>("email", "tanaka@example.com"));
順序はListが保持します。
C#foreach (var item in items)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
ただし、キー検索は自分で行う必要があります。
C#var item = items.FirstOrDefault(x => x.Key == "name");
要素数が少ない場合は問題になりにくいですが、キー検索が多い場合は効率が悪くなります。
8-3. LINQのOrderByで必要なタイミングだけ並べ替える
保存時に順序を持つ必要がなく、出力時だけ並べ替えたい場合は、LINQのOrderByが便利です。
C#Dictionary<string, int> scores = new Dictionary<string, int>
{
["Charlie"] = 85,
["Alice"] = 90,
["Bob"] = 80
};
foreach (var item in scores.OrderBy(x => x.Key))
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
出力例です。
Alice: 90
Bob: 80
Charlie: 85
OrderByは、キー順、値順、任意の条件で並べ替えたい場合に向いています。
一方で、追加順そのものを保持したい場合には不向きです。
8-4. SortedDictionaryでキー順に管理する
キー順に常に並べたい場合は、SortedDictionary<TKey,TValue>が適しています。
C#SortedDictionary<string, string> dict = new SortedDictionary<string, string>();
dict.Add("b", "B");
dict.Add("a", "A");
dict.Add("c", "C");
foreach (var item in dict)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
出力例です。
a: A
b: B
c: C
追加順ではなく、キー順で管理したい場合に使います。
8-5. ImmutableDictionaryやImmutableSortedDictionaryを検討する
変更不可のコレクションが必要な場合は、ImmutableDictionaryやImmutableSortedDictionaryを検討します。
これらは、一度作成したコレクションを直接変更せず、変更後の新しいコレクションを作る設計です。
マルチスレッド環境や、状態を安全に扱いたい設計で役立ちます。
ただし、ImmutableDictionaryは追加順を保持するための型ではありません。キー順が必要ならImmutableSortedDictionary、追加順が必要なら別の方法を検討します。
8-6. NuGetライブラリを使う選択肢
.NET 9より前の環境でジェネリックな順序付きDictionaryが必要な場合、NuGetライブラリを使う選択肢もあります。
たとえば、順序付きDictionary、双方向Dictionary、LRUキャッシュ向けコレクションなどを提供するライブラリがあります。
ただし、外部ライブラリを使う場合は、メンテナンス状況、ライセンス、依存関係、パフォーマンス、プロジェクト方針を確認する必要があります。
小規模な用途であれば自作やDictionaryとListの併用で十分な場合もあります。
8-7. 自作OrderedDictionaryが必要になるケース
要件が特殊な場合は、自作の順序付きDictionaryが必要になることもあります。
たとえば、次のようなケースです。
一定件数を超えたら古い要素を削除したい。アクセス順で並べ替えたい。キーの順序を自由に入れ替えたい。重複キーを許可したい。高速なインデックス検索とキー検索を両立したい。
このような場合、標準のOrderedDictionaryだけでは要件を満たせないことがあります。
ただし、自作すると実装とテストのコストが増えます。まずは標準ライブラリや既存ライブラリで対応できるかを検討し、それでも足りない場合に自作を考えるのがよいでしょう。
9. OrderedDictionaryの実践的な使いどころ
ここでは、OrderedDictionaryが実際に役立つ場面を具体的に見ていきます。
9-1. 設定ファイルの項目順を保持したい場合
設定ファイルでは、項目の順序が人間の読みやすさに影響します。
たとえば、次のような順序で出力したいとします。
AppName
Version
Environment
LogLevel
ConnectionString
OrderedDictionaryを使えば、登録した順番で設定項目を出力できます。
C#OrderedDictionary settings = new OrderedDictionary();
settings.Add("AppName", "SampleApp");
settings.Add("Version", "1.0.0");
settings.Add("Environment", "Production");
settings.Add("LogLevel", "Info");
settings.Add("ConnectionString", "...");
出力時はその順番で処理します。
C#foreach (DictionaryEntry entry in settings)
{
Console.WriteLine($"{entry.Key}={entry.Value}");
}
設定ファイルをレビューしやすくしたい場合に便利です。
9-2. JSONやCSVの出力順を制御したい場合
CSVでは列順が重要です。
C#OrderedDictionary row = new OrderedDictionary();
row.Add("Id", 1);
row.Add("Name", "田中");
row.Add("Email", "tanaka@example.com");
このように定義しておけば、CSVの列を登録順に出力できます。
C#foreach (DictionaryEntry entry in row)
{
Console.Write(entry.Value + ",");
}
JSONでも、人間が読むログや設定ファイルでは、項目順がそろっていると見やすくなります。
ただし、JSONの意味として順序に依存する設計は避けたほうがよい場合もあります。あくまで表示や可読性のために順序を制御する、と考えるとよいでしょう。
9-3. 画面表示や帳票出力で登録順を保ちたい場合
画面や帳票では、項目の順番がユーザー体験に直結します。
たとえば、ユーザー情報を表示する場合、次のような順序が自然です。
氏名
メールアドレス
電話番号
住所
登録日
OrderedDictionaryを使えば、項目名をキー、表示値を値として持ちながら、表示順を維持できます。
C#OrderedDictionary profile = new OrderedDictionary();
profile.Add("氏名", "田中 太郎");
profile.Add("メールアドレス", "tanaka@example.com");
profile.Add("電話番号", "090-xxxx-xxxx");
profile.Add("住所", "東京都...");
profile.Add("登録日", "2026-06-12");
帳票テンプレートや画面コンポーネントに渡すデータとして使いやすくなります。
9-4. メニューやフォーム項目を順序付きで管理したい場合
メニューやフォーム項目は、表示順が重要です。
C#OrderedDictionary menu = new OrderedDictionary();
menu.Add("home", "ホーム");
menu.Add("products", "商品一覧");
menu.Add("contact", "お問い合わせ");
キーは内部的な識別子として使い、値は表示名として使えます。
C#foreach (DictionaryEntry item in menu)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
フォーム項目でも同じ考え方が使えます。
C#OrderedDictionary formFields = new OrderedDictionary();
formFields.Add("name", "氏名");
formFields.Add("email", "メールアドレス");
formFields.Add("message", "お問い合わせ内容");
表示順を保ちつつ、キーで項目を識別できる点が便利です。
9-5. キャッシュや履歴データを順序付きで扱いたい場合
キャッシュや履歴データでは、追加順やアクセス順が重要になることがあります。
単純な追加順の履歴であれば、OrderedDictionaryで管理できます。
C#OrderedDictionary history = new OrderedDictionary();
history.Add("2026-06-10", "ログイン");
history.Add("2026-06-11", "設定変更");
history.Add("2026-06-12", "ログアウト");
ただし、本格的なLRUキャッシュのように「最近使った順」に並べ替えたい場合は、OrderedDictionaryだけでは不十分なことがあります。
その場合は、LinkedListとDictionaryを組み合わせる、専用ライブラリを使う、自作キャッシュを実装するなどの方法を検討します。
10. OrderedDictionaryに関するよくある質問
最後に、C#のOrderedDictionaryに関するよくある質問をまとめます。
10-1. C#のDictionaryは追加順を保証しますか?
いいえ、Dictionary<TKey,TValue>の列挙順は仕様上未定義です。公式ドキュメントでも、Dictionary<TKey,TValue>で項目が返される順序は未定義とされています。
そのため、追加順に依存する処理ではDictionaryではなく、OrderedDictionaryや別の順序付きコレクションを使うべきです。
10-2. OrderedDictionaryはジェネリックで使えますか?
.NET 9以降では、OrderedDictionary<TKey,TValue>が使えます。従来のSystem.Collections.Specialized.OrderedDictionaryは非ジェネリック型ですが、.NET 9でSystem.Collections.Generic.OrderedDictionary<TKey,TValue>が導入されました。
.NET 9以降を使えるなら、基本的にはジェネリック版を優先するとよいでしょう。
10-3. OrderedDictionaryとSortedDictionaryはどちらを使うべきですか?
追加順を保持したい場合はOrderedDictionary、キー順に並べたい場合はSortedDictionary<TKey,TValue>を使います。
たとえば、ユーザーが登録した順番で項目を表示したいならOrderedDictionaryです。商品コードや日付などのキー順に常に並べたいならSortedDictionaryです。
10-4. OrderedDictionaryでキー順に並べ替えできますか?
OrderedDictionary自体は、キー順に自動で並べ替えるためのコレクションではありません。
キー順に並べたい場合は、SortedDictionary<TKey,TValue>を使うか、出力時にLINQのOrderByを使います。
C#foreach (DictionaryEntry entry in dict.Cast<DictionaryEntry>().OrderBy(x => x.Key))
{
Console.WriteLine($"{entry.Key}: {entry.Value}");
}
ただし、非ジェネリック版ではCast<DictionaryEntry>()が必要になる点に注意してください。
10-5. OrderedDictionaryはパフォーマンスが悪いですか?
用途によります。
キー検索だけが目的なら、通常のDictionary<TKey,TValue>のほうがシンプルで効率的です。一方で、順序管理も必要な場合は、OrderedDictionaryを使う価値があります。
ジェネリック版OrderedDictionary<TKey,TValue>では、キー検索はDictionary<TKey,TValue>に近く、それ以外の操作はList<T>に近い複雑さを持つと公式ドキュメントで説明されています。
つまり、順序が不要ならDictionary、順序が必要ならOrderedDictionaryという判断が基本です。
10-6. .NET FrameworkでもOrderedDictionaryは使えますか?
はい、従来の非ジェネリック版System.Collections.Specialized.OrderedDictionaryは.NET Frameworkでも利用できます。
ただし、OrderedDictionary<TKey,TValue>は.NET 9以降のジェネリック版です。.NET Frameworkで型安全な順序付きDictionaryが必要な場合は、DictionaryとListを組み合わせる、外部ライブラリを使う、自作するなどの方法を検討します。
10-7. 順序を保持するDictionaryを自作する必要はありますか?
多くの場合、自作する前に標準ライブラリで対応できるかを確認するべきです。
.NET 9以降ならOrderedDictionary<TKey,TValue>が有力です。.NET 8以前や.NET Frameworkでは、非ジェネリック版OrderedDictionary、DictionaryとListの併用、List<KeyValuePair<TKey,TValue>>、SortedDictionary、外部ライブラリなどを検討できます。
自作が必要になるのは、アクセス順で並べ替える、独自の削除ルールを持つ、重複キーを許可する、特殊なパフォーマンス要件があるなど、標準機能では足りない場合です。
まとめ
C#で順序を保持するDictionaryを使いたい場合、OrderedDictionaryは有力な選択肢です。
通常のDictionary<TKey,TValue>はキー検索に便利ですが、列挙順は未定義です。そのため、追加順や表示順、出力順に依存する処理では、Dictionaryの順序に頼るべきではありません。
OrderedDictionaryを使うと、キーで値を取得しながら、追加順やインデックスも扱えます。従来のSystem.Collections.Specialized.OrderedDictionaryは非ジェネリック型のため、DictionaryEntryやキャストが必要ですが、.NET 9以降ではジェネリック版OrderedDictionary<TKey,TValue>を使えるため、より型安全に扱えます。
使い分けの基本は次のとおりです。
キー検索だけでよいならDictionary<TKey,TValue>。追加順を保持したいならOrderedDictionary。.NET 9以降で型安全に扱いたいならOrderedDictionary<TKey,TValue>。キー順に並べたいならSortedDictionary<TKey,TValue>やSortedList<TKey,TValue>。必要なタイミングだけ並べ替えるならLINQのOrderBy。
C#でc# ordered dictionaryを調べている方は、「順序が本当に必要か」「必要な順序は追加順かキー順か」「利用している.NETのバージョンは何か」を確認すると、最適なコレクションを選びやすくなります。

