C# SortedDictionaryの使い方完全ガイド|Dictionary・SortedListとの違いと選び方を実例で解説

はじめに

C#でキーと値を組み合わせてデータを管理するとき、まず思い浮かぶのはDictionary<TKey, TValue>です。しかし、キーを常に昇順で管理したい場合や、ループ処理でキー順に取り出したい場合は、SortedDictionary<TKey, TValue>が便利です。

SortedDictionaryは、キーと値のペアを保持しながら、キーの順序に従って自動的に並び替えてくれるコレクションです。たとえば、商品ID順に在庫を表示したい、設定値をキー名順に出力したい、スコアや優先度を昇順に処理したい、といった場面で役立ちます。

この記事では、C#のSortedDictionaryの基本的な使い方から、DictionarySortedListとの違い、実務での選び方、よくあるエラーの対処法まで、サンプルコードを交えてわかりやすく解説します。

1. C#のSortedDictionaryとは

1-1. SortedDictionaryの基本的な役割

SortedDictionary<TKey, TValue>は、キーと値をセットで管理するジェネリックコレクションです。

基本的な考え方はDictionary<TKey, TValue>と同じで、キーを使って値を登録・取得します。ただし、大きな違いとして、SortedDictionaryはキーの順序に従って要素を管理します。

C#
var scores = new SortedDictionary<int, string>();

scores.Add(3, "Charlie");
scores.Add(1, "Alice");
scores.Add(2, "Bob");

foreach (var item in scores)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}

実行結果は次のようになります。

1: Alice
2: Bob
3: Charlie

追加した順番は3 → 1 → 2ですが、出力時にはキーの昇順である1 → 2 → 3に並びます。

SortedDictionary<TKey, TValue>は内部的に二分探索木として実装され、検索は要素数をnとしたときにO(log n)で行われます。Microsoft公式ドキュメントでも、SortedDictionaryは二分探索木であり、取得処理がO(log n)であると説明されています。Microsoft Learn

1-2. キーを自動で並び替えて管理できる仕組み

SortedDictionaryでは、キーの比較結果をもとに要素の並び順が決まります。

たとえば、キーがintなら数値の小さい順、キーがstringなら既定の文字列比較順で並びます。

C#
var fruits = new SortedDictionary<string, int>();

fruits.Add("banana", 120);
fruits.Add("apple", 100);
fruits.Add("orange", 150);

foreach (var fruit in fruits)
{
Console.WriteLine($"{fruit.Key}: {fruit.Value}");
}

実行結果は次のようになります。

apple: 100
banana: 120
orange: 150

このように、SortedDictionaryは追加時に自動的にキー順で管理されるため、あとからOrderByなどで並び替えなくても、foreachで取り出すだけでキー順のデータを取得できます。

1-3. DictionaryやListではなくSortedDictionaryを使う場面

SortedDictionaryを使うべきなのは、キーと値の組み合わせを管理しつつ、常にキー順で取り出したい場面です。

たとえば、次のようなケースに向いています。

C#
var productStock = new SortedDictionary<int, string>();

productStock.Add(1003, "キーボード");
productStock.Add(1001, "マウス");
productStock.Add(1002, "モニター");

foreach (var product in productStock)
{
Console.WriteLine($"{product.Key}: {product.Value}");
}

実行結果は次のようになります。

1001: マウス
1002: モニター
1003: キーボード

商品ID順、日付順、優先度順、コード順など、キーに意味のある順序がある場合はSortedDictionaryが便利です。

一方、キー順が不要で、単に高速にキー検索したいだけならDictionaryの方が適しています。また、単純な一覧を順番に保持したいだけならListで十分です。

1-4. SortedDictionaryでできること・できないこと

SortedDictionaryでできる主なことは、キーと値の追加、取得、更新、削除、キーの存在確認、キー順での列挙です。

C#
var dictionary = new SortedDictionary<string, int>();

dictionary.Add("A", 10);
dictionary["B"] = 20;

Console.WriteLine(dictionary["A"]);

一方で、できないことや注意すべきこともあります。

SortedDictionaryでは、同じキーを重複して登録できません。また、並び順は値ではなくキーによって決まります。値の大きい順に並べたい場合は、OrderByOrderByDescendingを使って一時的に並び替える必要があります。

さらに、SortedDictionaryはリストのようなインデックスアクセスには向いていません。dictionary[0]のように「0番目の要素」を取得する使い方ではなく、dictionary[key]のようにキーを指定して値を取得します。

2. SortedDictionaryの基本的な使い方

2-1. SortedDictionaryの宣言と初期化

SortedDictionaryを使うには、System.Collections.Generic名前空間を利用します。

C#
using System.Collections.Generic;

基本的な宣言方法は次の通りです。

C#
SortedDictionary<int, string> users = new SortedDictionary<int, string>();

C#の型推論を使うと、次のように簡潔に書けます。

C#
var users = new SortedDictionary<int, string>();

初期値を指定して作成することもできます。

C#
var users = new SortedDictionary<int, string>
{
{ 3, "Charlie" },
{ 1, "Alice" },
{ 2, "Bob" }
};

この場合も、内部ではキー順で管理されます。

C#
foreach (var user in users)
{
Console.WriteLine($"{user.Key}: {user.Value}");
}

実行結果は次のようになります。

1: Alice
2: Bob
3: Charlie

2-2. 要素を追加する方法

要素を追加するには、Addメソッドを使います。

C#
var prices = new SortedDictionary<string, int>();

prices.Add("apple", 100);
prices.Add("banana", 120);
prices.Add("orange", 150);

また、インデクサーを使って追加することもできます。

C#
prices["grape"] = 200;

Addとインデクサーには違いがあります。

Addは、すでに同じキーが存在する場合に例外を発生させます。一方、インデクサーは、キーが存在しない場合は追加し、存在する場合は値を上書きします。Microsoft公式ドキュメントでも、Addメソッドは重複キーを追加しようとするとArgumentExceptionをスローすると説明されています。Microsoft Learn

C#
var items = new SortedDictionary<string, int>();

items.Add("A", 1);
// items.Add("A", 2); // ArgumentException

items["A"] = 2; // 更新される

2-3. キーを指定して値を取得する方法

値を取得するには、キーを指定します。

C#
var ages = new SortedDictionary<string, int>
{
{ "Alice", 25 },
{ "Bob", 30 }
};

Console.WriteLine(ages["Alice"]);

実行結果は次の通りです。

25

ただし、存在しないキーを指定するとKeyNotFoundExceptionが発生します。

C#
Console.WriteLine(ages["Charlie"]); // KeyNotFoundException

安全に値を取得したい場合は、後述するContainsKeyTryGetValueを使いましょう。

2-4. 要素を更新する方法

既存のキーに対応する値を更新するには、インデクサーを使います。

C#
var stock = new SortedDictionary<string, int>
{
{ "apple", 10 },
{ "banana", 5 }
};

stock["apple"] = 20;

Console.WriteLine(stock["apple"]);

実行結果は次のようになります。

20

インデクサーは、キーが存在する場合は更新、存在しない場合は追加になります。

C#
stock["orange"] = 8;

このコードでは、orangeというキーが存在しないため、新しい要素として追加されます。

2-5. 要素を削除する方法

要素を削除するには、Removeメソッドを使います。

C#
var stock = new SortedDictionary<string, int>
{
{ "apple", 10 },
{ "banana", 5 },
{ "orange", 8 }
};

stock.Remove("banana");

foreach (var item in stock)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}

実行結果は次のようになります。

apple: 10
orange: 8

Removeメソッドは、削除に成功した場合はtrue、指定したキーが存在しなかった場合はfalseを返します。

C#
bool removed = stock.Remove("banana");

if (removed)
{
Console.WriteLine("削除しました");
}
else
{
Console.WriteLine("キーが存在しません");
}

2-6. ContainsKey・TryGetValueで安全に検索する方法

存在しないキーをインデクサーで取得すると例外が発生します。そのため、事前にキーの存在を確認することが大切です。

ContainsKeyを使うと、指定したキーが存在するか確認できます。

C#
var users = new SortedDictionary<int, string>
{
{ 1, "Alice" },
{ 2, "Bob" }
};

if (users.ContainsKey(1))
{
Console.WriteLine(users[1]);
}

より実務でよく使われるのはTryGetValueです。

C#
if (users.TryGetValue(2, out string? name))
{
Console.WriteLine(name);
}
else
{
Console.WriteLine("ユーザーが見つかりません");
}

TryGetValueは、キーが存在すればtrueを返し、値をout引数に格納します。キーが存在しない場合はfalseを返すため、例外を避けながら安全に検索できます。

2-7. foreachでキー順にループ処理する方法

SortedDictionaryの大きな特徴は、foreachで列挙するとキー順で処理されることです。

C#
var tasks = new SortedDictionary<int, string>
{
{ 3, "レポート作成" },
{ 1, "メール確認" },
{ 2, "会議準備" }
};

foreach (var task in tasks)
{
Console.WriteLine($"{task.Key}: {task.Value}");
}

実行結果は次の通りです。

1: メール確認
2: 会議準備
3: レポート作成

foreachで取り出される要素は、KeyValuePair<TKey, TValue>です。Microsoft公式ドキュメントでも、foreachで列挙される要素はキーや値そのものではなく、KeyValuePair<TKey, TValue>であると説明されています。Microsoft Learn

3. SortedDictionaryの実例コード

3-1. 数値キーを昇順に並べるサンプル

数値キーを使うと、自然に小さい順で並びます。

C#
using System;
using System.Collections.Generic;

class Program
{
static void Main()
{
var ranking = new SortedDictionary<int, string>
{
{ 3, "Charlie" },
{ 1, "Alice" },
{ 2, "Bob" }
};

foreach (var item in ranking)
{
Console.WriteLine($"{item.Key}位: {item.Value}");
}
}
}

実行結果は次のようになります。

1位: Alice
2位: Bob
3位: Charlie

ランキングや優先度、商品ID、連番など、数値に意味がある場合に使いやすいです。

3-2. 文字列キーをアルファベット順に並べるサンプル

文字列キーの場合は、既定の比較方法に従って並びます。

C#
var colors = new SortedDictionary<string, string>
{
{ "blue", "青" },
{ "red", "赤" },
{ "green", "緑" }
};

foreach (var color in colors)
{
Console.WriteLine($"{color.Key}: {color.Value}");
}

実行結果は次の通りです。

blue: 青
green: 緑
red: 赤

設定ファイルのキー、コード名、カテゴリ名などをキー順に表示したい場合に便利です。

3-3. 在庫管理・ランキング・設定値管理での活用例

在庫管理では、商品コードをキーにすると商品コード順に表示できます。

C#
var inventory = new SortedDictionary<int, string>
{
{ 3002, "USBケーブル" },
{ 1001, "マウス" },
{ 2003, "キーボード" }
};

foreach (var item in inventory)
{
Console.WriteLine($"商品コード: {item.Key}, 商品名: {item.Value}");
}

ランキング管理では、順位をキーにできます。

C#
var ranking = new SortedDictionary<int, string>
{
{ 2, "田中" },
{ 1, "佐藤" },
{ 3, "鈴木" }
};

foreach (var rank in ranking)
{
Console.WriteLine($"{rank.Key}位: {rank.Value}");
}

設定値管理では、設定名をキーにすると、出力時に見やすくなります。

C#
var settings = new SortedDictionary<string, string>
{
{ "Theme", "Dark" },
{ "Language", "Japanese" },
{ "FontSize", "14" }
};

foreach (var setting in settings)
{
Console.WriteLine($"{setting.Key} = {setting.Value}");
}

3-4. KeyValuePairを使った値の取り出し方

SortedDictionaryforeachで回すと、各要素はKeyValuePair<TKey, TValue>として取得されます。

C#
var users = new SortedDictionary<int, string>
{
{ 1, "Alice" },
{ 2, "Bob" }
};

foreach (KeyValuePair<int, string> user in users)
{
int id = user.Key;
string name = user.Value;

Console.WriteLine($"ID: {id}, Name: {name}");
}

varを使うと簡潔に書けます。

C#
foreach (var user in users)
{
Console.WriteLine($"ID: {user.Key}, Name: {user.Value}");
}

キーだけを取り出したい場合はKeys、値だけを取り出したい場合はValuesを使います。

C#
foreach (var key in users.Keys)
{
Console.WriteLine(key);
}

foreach (var value in users.Values)
{
Console.WriteLine(value);
}

3-5. LINQと組み合わせて条件検索する方法

SortedDictionaryはLINQと組み合わせることもできます。

たとえば、価格が1000円以上の商品だけを抽出する例です。

C#
using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
static void Main()
{
var products = new SortedDictionary<int, int>
{
{ 1001, 800 },
{ 1002, 1200 },
{ 1003, 500 },
{ 1004, 2000 }
};

var expensiveProducts = products.Where(p => p.Value >= 1000);

foreach (var product in expensiveProducts)
{
Console.WriteLine($"商品ID: {product.Key}, 価格: {product.Value}");
}
}
}

実行結果は次のようになります。

商品ID: 1002, 価格: 1200
商品ID: 1004, 価格: 2000

値で並び替えたい場合は、OrderByOrderByDescendingを使います。

C#
var sortedByPrice = products.OrderBy(p => p.Value);

foreach (var product in sortedByPrice)
{
Console.WriteLine($"商品ID: {product.Key}, 価格: {product.Value}");
}

ただし、この場合に並び替えられているのはLINQの結果であり、元のSortedDictionary自体の並び順が値順に変わるわけではありません。

4. SortedDictionaryの並び順をカスタマイズする方法

4-1. デフォルトの並び順

SortedDictionaryのデフォルトの並び順は、キー型の既定の比較方法によって決まります。

intであれば数値の昇順、stringであれば文字列の比較順になります。

C#
var numbers = new SortedDictionary<int, string>
{
{ 30, "thirty" },
{ 10, "ten" },
{ 20, "twenty" }
};

foreach (var number in numbers)
{
Console.WriteLine(number.Key);
}

実行結果は次の通りです。

10
20
30

4-2. 降順で並べる方法

降順で並べたい場合は、IComparer<TKey>を指定します。

C#
var descending = new SortedDictionary<int, string>(
Comparer<int>.Create((x, y) => y.CompareTo(x))
);

descending.Add(1, "Alice");
descending.Add(3, "Charlie");
descending.Add(2, "Bob");

foreach (var item in descending)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}

実行結果は次のようになります。

3: Charlie
2: Bob
1: Alice

SortedDictionaryのコンストラクターに比較ロジックを渡すことで、キーの並び順を自由に変えられます。

4-3. IComparerを使って独自の並び順を指定する方法

より複雑な比較を行いたい場合は、IComparer<TKey>を実装したクラスを作成します。

C#
using System;
using System.Collections.Generic;

class DescendingComparer : IComparer<int>
{
public int Compare(int x, int y)
{
return y.CompareTo(x);
}
}

class Program
{
static void Main()
{
var dictionary = new SortedDictionary<int, string>(new DescendingComparer())
{
{ 1, "A" },
{ 3, "C" },
{ 2, "B" }
};

foreach (var item in dictionary)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}
}
}

実行結果は次の通りです。

3: C
2: B
1: A

比較ルールを複数箇所で再利用したい場合は、ラムダ式よりもIComparerクラスとして定義しておくと管理しやすくなります。

4-4. 大文字・小文字を区別しない文字列キーの並び替え

文字列キーで大文字・小文字を区別したくない場合は、StringComparer.OrdinalIgnoreCaseを指定します。

C#
var dictionary = new SortedDictionary<string, int>(
StringComparer.OrdinalIgnoreCase
);

dictionary.Add("apple", 100);
dictionary.Add("Banana", 120);
dictionary.Add("orange", 150);

foreach (var item in dictionary)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}

このようにすると、キーの比較時に大文字・小文字の違いを無視できます。

ただし、比較ルール上同じと判断されるキーは重複キーとして扱われます。

C#
var dictionary = new SortedDictionary<string, int>(
StringComparer.OrdinalIgnoreCase
);

dictionary.Add("apple", 100);
// dictionary.Add("APPLE", 200); // 同じキーとみなされ、例外になる

4-5. 独自クラスをキーにする場合の注意点

独自クラスをキーにする場合は、比較方法を明確にする必要があります。

たとえば、次のようなProductクラスをキーにする場合、そのままではどのように並べるべきか判断できません。

C#
class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
}

この場合は、IComparer<Product>を作成します。

C#
class ProductComparer : IComparer<Product>
{
public int Compare(Product? x, Product? y)
{
if (x == null || y == null)
{
throw new ArgumentNullException();
}

return x.Id.CompareTo(y.Id);
}
}

使用例は次の通りです。

C#
var products = new SortedDictionary<Product, int>(new ProductComparer());

products.Add(new Product { Id = 2, Name = "Keyboard" }, 5000);
products.Add(new Product { Id = 1, Name = "Mouse" }, 2000);

foreach (var product in products)
{
Console.WriteLine($"{product.Key.Id}: {product.Key.Name} - {product.Value}");
}

独自クラスをキーにする場合は、キーとして使っているプロパティを後から変更しないことも重要です。Microsoft公式ドキュメントでも、SortedDictionaryのキーはキーとして使用されている間は不変である必要があると説明されています。Microsoft Learn

5. SortedDictionaryとDictionaryの違い

5-1. Dictionaryはキー順を保証しない

Dictionary<TKey, TValue>は、キーを使って高速に値を取得するためのコレクションです。

C#
var dictionary = new Dictionary<int, string>
{
{ 3, "Charlie" },
{ 1, "Alice" },
{ 2, "Bob" }
};

Dictionaryはキー順に並べることを目的としたコレクションではありません。キー順で処理したい場合は、OrderByを使って並び替える必要があります。

C#
foreach (var item in dictionary.OrderBy(x => x.Key))
{
Console.WriteLine($"{item.Key}: {item.Value}");
}

5-2. SortedDictionaryは常にキー順で管理される

SortedDictionaryは、要素を追加した時点でキー順に管理されます。

C#
var sortedDictionary = new SortedDictionary<int, string>
{
{ 3, "Charlie" },
{ 1, "Alice" },
{ 2, "Bob" }
};

foreach (var item in sortedDictionary)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}

実行結果は次のようになります。

1: Alice
2: Bob
3: Charlie

毎回キー順で処理したい場合は、Dictionaryに対して都度OrderByするより、最初からSortedDictionaryを使う方がコードの意図が明確になります。

5-3. 検索・追加・削除のパフォーマンス比較

DictionarySortedDictionaryの大きな違いは、並び順とパフォーマンスです。

一般的に、キー順が不要で検索速度を重視するならDictionaryが有利です。一方、キー順を常に維持したい場合はSortedDictionaryが便利です。

SortedDictionaryは二分探索木を使い、取得処理はO(log n)です。また、SortedDictionaryは未ソートデータに対する挿入・削除がSortedListより速いとMicrosoft公式ドキュメントで説明されています。Microsoft Learn+1

ざっくり比較すると、次のようになります。

コレクションキー順検索追加・削除
Dictionary保証しない高速高速
SortedDictionary保証するO(log n)O(log n)
SortedList保証するO(log n)追加・削除は状況により重い

5-4. メモリ使用量の違い

DictionarySortedDictionaryでは内部構造が異なるため、メモリ使用量にも違いがあります。

SortedDictionaryはキー順を維持するための構造を持つため、単純にキーと値を高速検索するだけの用途では、Dictionaryの方が扱いやすいことが多いです。

また、SortedDictionarySortedListを比較すると、Microsoft公式ドキュメントではSortedList<TKey, TValue>の方がSortedDictionary<TKey, TValue>より少ないメモリを使用すると説明されています。Microsoft Learn+1

5-5. Dictionaryを使うべきケース

Dictionaryを使うべきなのは、キー順が不要で、とにかくキーから値を取得できればよい場合です。

たとえば、次のようなケースです。

C#
var userNames = new Dictionary<int, string>();

userNames[1] = "Alice";
userNames[2] = "Bob";

Console.WriteLine(userNames[1]);

ユーザーIDからユーザー名を取得する、商品コードから商品情報を取得する、キャッシュとして値を保持する、といった用途ではDictionaryが適しています。

キー順で出力する必要がほとんどないなら、無理にSortedDictionaryを使う必要はありません。

5-6. SortedDictionaryを使うべきケース

SortedDictionaryを使うべきなのは、キー順での処理が重要な場合です。

たとえば、次のようなケースです。

C#
var events = new SortedDictionary<DateTime, string>();

events.Add(new DateTime(2026, 1, 10), "会議");
events.Add(new DateTime(2026, 1, 5), "資料提出");
events.Add(new DateTime(2026, 1, 20), "レビュー");

foreach (var item in events)
{
Console.WriteLine($"{item.Key:yyyy/MM/dd}: {item.Value}");
}

実行結果は日付順になります。

2026/01/05: 資料提出
2026/01/10: 会議
2026/01/20: レビュー

日付順、番号順、優先度順など、キーの順序がそのまま処理順になる場合はSortedDictionaryが向いています。

6. SortedDictionaryとSortedListの違い

6-1. SortedListとは

SortedList<TKey, TValue>も、キーと値を管理しながらキー順に並べるコレクションです。

C#
var sortedList = new SortedList<int, string>
{
{ 3, "Charlie" },
{ 1, "Alice" },
{ 2, "Bob" }
};

foreach (var item in sortedList)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}

実行結果は次の通りです。

1: Alice
2: Bob
3: Charlie

見た目の使い方はSortedDictionaryとよく似ています。

6-2. SortedDictionaryとSortedListの内部構造の違い

SortedDictionarySortedListは、どちらもキー順に要素を管理しますが、内部構造が異なります。

SortedDictionaryは二分探索木として実装されています。一方、SortedListはキーと値の配列として実装されています。Microsoft公式ドキュメントでも、SortedList<TKey, TValue>はキーでソートされたキーと値の配列として実装されていると説明されています。Microsoft Learn

この内部構造の違いにより、追加・削除・メモリ使用量・インデックスアクセスの性質が変わります。

6-3. 追加・削除が多い場合の選び方

追加や削除が多い場合は、SortedDictionaryが向いています。

SortedListは内部的に配列を使うため、途中に要素を挿入したり削除したりすると、要素の移動が発生することがあります。そのため、未ソートデータを頻繁に追加・削除する場合はSortedDictionaryの方が有利です。

Microsoft公式ドキュメントでも、未ソートデータに対する挿入・削除はSortedDictionaryO(log n)であるのに対し、SortedListO(n)であると説明されています。Microsoft Learn+1

6-4. 参照が多い場合の選び方

参照が中心で、データの追加や削除が少ない場合は、SortedListも選択肢になります。

SortedListはメモリ使用量が少なく、データをまとめて作成したあとに読み取り中心で使う場合に向いています。

特に、最初にデータを一括登録し、その後はほとんど変更せずに参照するようなケースでは、SortedListを検討できます。

6-5. インデックスアクセスの有無

SortedListは、キーだけでなくインデックスを使ったアクセスもできます。

C#
var sortedList = new SortedList<int, string>
{
{ 10, "A" },
{ 20, "B" },
{ 30, "C" }
};

Console.WriteLine(sortedList.Keys[0]);
Console.WriteLine(sortedList.Values[0]);

実行結果は次のようになります。

10
A

一方、SortedDictionaryはインデックスアクセスには向いていません。SortedDictionaryで「先頭の要素」を取りたい場合は、LINQのFirst()などを使います。

C#
var first = dictionary.First();
Console.WriteLine($"{first.Key}: {first.Value}");

6-6. SortedDictionaryとSortedListの使い分け早見表

SortedDictionarySortedListの違いを簡単にまとめると、次のようになります。

比較項目SortedDictionarySortedList
並び順キー順キー順
内部構造二分探索木配列
検索O(log n)O(log n)
追加・削除比較的得意件数が多いと重くなりやすい
メモリ使用量SortedListより多め比較的少ない
インデックスアクセス不向き可能
向いている用途追加・削除が多いキー順管理参照中心のキー順管理

追加・削除が多いならSortedDictionary、読み取り中心でメモリ効率やインデックスアクセスを重視するならSortedListを検討するとよいでしょう。

7. SortedDictionary・Dictionary・SortedListの選び方

7-1. キー順が不要ならDictionary

キー順が不要なら、まずDictionaryを選ぶのが基本です。

C#
var cache = new Dictionary<string, string>();

cache["user:1"] = "Alice";
cache["user:2"] = "Bob";

キーから値を取り出せればよく、並び順を気にしない場合はDictionaryがシンプルです。

7-2. キー順が必要で追加・削除が多いならSortedDictionary

キー順が必要で、データの追加や削除が頻繁に発生する場合はSortedDictionaryが向いています。

C#
var schedule = new SortedDictionary<DateTime, string>();

schedule.Add(DateTime.Parse("2026-01-10"), "会議");
schedule.Add(DateTime.Parse("2026-01-05"), "資料提出");
schedule.Add(DateTime.Parse("2026-01-20"), "レビュー");

schedule.Remove(DateTime.Parse("2026-01-10"));

常に日付順で管理しながら、予定の追加や削除を行うようなケースに適しています。

7-3. キー順が必要で参照中心ならSortedList

キー順が必要でも、データの変更が少なく、参照が中心ならSortedListを検討できます。

C#
var prefectures = new SortedList<int, string>
{
{ 13, "東京都" },
{ 27, "大阪府" },
{ 1, "北海道" }
};

最初にデータを作って、その後は読み取り中心で使う場合には、SortedListが扱いやすいことがあります。

7-4. 件数が少ない場合の選び方

件数が少ない場合は、DictionarySortedDictionarySortedListのパフォーマンス差は大きな問題になりにくいです。

その場合は、コードの意図が伝わりやすいものを選ぶとよいでしょう。

キー順が必要ないならDictionary、キー順で処理したいならSortedDictionaryまたはSortedListを使います。迷った場合は、追加・削除がある程度発生するならSortedDictionaryを選ぶと無難です。

7-5. 実務で迷ったときの判断基準

実務で迷ったときは、次の基準で選ぶと判断しやすくなります。

やりたいこと選ぶコレクション
キーで高速に値を取得したいDictionary
キー順で常に管理したいSortedDictionary
キー順で、追加・削除も多いSortedDictionary
キー順で、読み取り中心SortedList
インデックスでアクセスしたいSortedList
値で並び替えたいLINQのOrderBy

重要なのは、「並び順が必要か」「追加・削除が多いか」「値で並べたいのか、キーで並べたいのか」を明確にすることです。

8. SortedDictionaryを使うときの注意点

8-1. キーの重複はできない

SortedDictionaryでは、同じキーを複数登録できません。

C#
var dictionary = new SortedDictionary<int, string>();

dictionary.Add(1, "Alice");
// dictionary.Add(1, "Bob"); // ArgumentException

同じキーに対して値を変更したい場合は、Addではなくインデクサーを使います。

C#
dictionary[1] = "Bob";

複数の値を同じキーに関連付けたい場合は、値をList<T>にする方法があります。

C#
var dictionary = new SortedDictionary<int, List<string>>();

dictionary[1] = new List<string> { "Alice", "Bob" };

8-2. 存在しないキーを参照すると例外が発生する

存在しないキーをインデクサーで取得すると、KeyNotFoundExceptionが発生します。Microsoft公式ドキュメントでも、指定したキーが存在しない状態でインデクサーから取得しようとするとKeyNotFoundExceptionがスローされると説明されています。Microsoft Learn

C#
var dictionary = new SortedDictionary<string, int>();

Console.WriteLine(dictionary["apple"]); // KeyNotFoundException

安全に取得するには、TryGetValueを使いましょう。

C#
if (dictionary.TryGetValue("apple", out int value))
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("キーが存在しません");
}

8-3. nullキーは使えない

SortedDictionaryでは、キーにnullは使えません。

C#
var dictionary = new SortedDictionary<string, int>();

// dictionary.Add(null, 100); // ArgumentNullException

Microsoft公式ドキュメントでも、SortedDictionaryのキーはnullにできない一方、値は参照型であればnullにできると説明されています。Microsoft Learn

C#
var dictionary = new SortedDictionary<string, string?>();

dictionary.Add("A", null); // 値のnullは可能

8-4. 並び順はキーで決まり値では決まらない

SortedDictionaryの並び順はキーで決まります。

C#
var scores = new SortedDictionary<string, int>
{
{ "Alice", 90 },
{ "Bob", 70 },
{ "Charlie", 80 }
};

foreach (var score in scores)
{
Console.WriteLine($"{score.Key}: {score.Value}");
}

この場合、スコアの値ではなく、名前のキー順で並びます。

値で並べたい場合は、LINQを使います。

C#
foreach (var score in scores.OrderByDescending(x => x.Value))
{
Console.WriteLine($"{score.Key}: {score.Value}");
}

8-5. 列挙中に要素を変更するとエラーになる

foreachで列挙している最中に、SortedDictionaryの要素を追加・削除するとInvalidOperationExceptionが発生します。

C#
var dictionary = new SortedDictionary<int, string>
{
{ 1, "A" },
{ 2, "B" },
{ 3, "C" }
};

foreach (var item in dictionary)
{
// dictionary.Remove(item.Key); // InvalidOperationException
}

削除したい場合は、削除対象のキーを一度リストに保存してから削除します。

C#
var removeKeys = dictionary
.Where(x => x.Key % 2 == 1)
.Select(x => x.Key)
.ToList();

foreach (var key in removeKeys)
{
dictionary.Remove(key);
}

8-6. パフォーマンスを意識すべきケース

SortedDictionaryは便利ですが、常に最適とは限りません。

キー順が不要なのにSortedDictionaryを使うと、並び順を維持するためのコストが無駄になることがあります。単純なキー検索だけならDictionary、キー順で読み取り中心ならSortedListも候補になります。

また、値で並び替える処理が中心なら、SortedDictionaryではなく、List<T>Dictionaryに対してLINQのOrderByを使う方が自然な場合もあります。

9. SortedDictionaryでよくあるエラーと対処法

9-1. 同じキーをAddして例外が出る場合

同じキーをAddしようとすると、ArgumentExceptionが発生します。

C#
var dictionary = new SortedDictionary<int, string>();

dictionary.Add(1, "Alice");
dictionary.Add(1, "Bob"); // ArgumentException

対処法は、キーが存在するか確認することです。

C#
if (!dictionary.ContainsKey(1))
{
dictionary.Add(1, "Bob");
}

また、既存なら更新、なければ追加という動きでよい場合は、インデクサーを使います。

C#
dictionary[1] = "Bob";

9-2. KeyNotFoundExceptionが発生する場合

存在しないキーを取得すると、KeyNotFoundExceptionが発生します。

C#
var dictionary = new SortedDictionary<string, int>();

int value = dictionary["apple"]; // KeyNotFoundException

対処法は、TryGetValueを使うことです。

C#
if (dictionary.TryGetValue("apple", out int value))
{
Console.WriteLine(value);
}
else
{
Console.WriteLine("appleは登録されていません");
}

TryGetValueを使うと、キーの存在確認と値の取得を同時に行えるため、実務ではよく使われます。

9-3. キーの比較方法が原因で期待通りに並ばない場合

文字列キーを使う場合、大文字・小文字やカルチャによる比較の違いで、期待と異なる順序になることがあります。

C#
var dictionary = new SortedDictionary<string, int>
{
{ "apple", 1 },
{ "Banana", 2 },
{ "orange", 3 }
};

大文字・小文字を区別せずに扱いたい場合は、比較方法を明示します。

C#
var dictionary = new SortedDictionary<string, int>(
StringComparer.OrdinalIgnoreCase
);

文字列の比較ルールを明確にしておくと、環境やデータによる意図しない並び順を避けやすくなります。

9-4. 独自クラスをキーにして比較エラーが出る場合

独自クラスをキーにしたとき、比較方法が定義されていないとエラーになることがあります。

C#
class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
}

このようなクラスをキーにする場合は、IComparer<User>を指定します。

C#
class UserComparer : IComparer<User>
{
public int Compare(User? x, User? y)
{
if (x == null || y == null)
{
throw new ArgumentNullException();
}

return x.Id.CompareTo(y.Id);
}
}

使用例は次の通りです。

C#
var users = new SortedDictionary<User, string>(new UserComparer());

users.Add(new User { Id = 2, Name = "Bob" }, "一般ユーザー");
users.Add(new User { Id = 1, Name = "Alice" }, "管理者");

独自クラスをキーにする場合は、「何を基準に並べるのか」を必ず定義しましょう。

9-5. foreach中の変更でInvalidOperationExceptionが出る場合

foreach中に要素を削除すると、InvalidOperationExceptionが発生します。

C#
foreach (var item in dictionary)
{
if (item.Key == 1)
{
dictionary.Remove(item.Key); // エラー
}
}

対処法は、変更対象を先に別コレクションへ取り出しておくことです。

C#
var keysToRemove = dictionary
.Where(x => x.Key == 1)
.Select(x => x.Key)
.ToList();

foreach (var key in keysToRemove)
{
dictionary.Remove(key);
}

列挙中のコレクションを直接変更しない、というルールを覚えておきましょう。

10. SortedDictionaryに関するよくある質問

10-1. SortedDictionaryはキー順と追加順のどちらで並ぶ?

SortedDictionaryは追加順ではなくキー順で並びます。

C#
var dictionary = new SortedDictionary<int, string>
{
{ 3, "C" },
{ 1, "A" },
{ 2, "B" }
};

foreach (var item in dictionary)
{
Console.WriteLine(item.Key);
}

実行結果は次の通りです。

1
2
3

追加した順番を保持したい場合は、List<T>など別のコレクションを検討しましょう。

10-2. SortedDictionaryで降順に並べられる?

はい、可能です。

コンストラクターに降順用のIComparerを渡します。

C#
var dictionary = new SortedDictionary<int, string>(
Comparer<int>.Create((x, y) => y.CompareTo(x))
);

dictionary.Add(1, "A");
dictionary.Add(3, "C");
dictionary.Add(2, "B");

foreach (var item in dictionary)
{
Console.WriteLine(item.Key);
}

実行結果は次のようになります。

3
2
1

10-3. SortedDictionaryは値でソートできる?

SortedDictionary自体の並び順はキーで決まるため、値で管理することはできません。

ただし、LINQを使って値順に並び替えた結果を取得することはできます。

C#
var scores = new SortedDictionary<string, int>
{
{ "Alice", 90 },
{ "Bob", 70 },
{ "Charlie", 80 }
};

var sortedByValue = scores.OrderByDescending(x => x.Value);

foreach (var item in sortedByValue)
{
Console.WriteLine($"{item.Key}: {item.Value}");
}

値順で頻繁に管理したい場合は、データ構造の設計を見直した方がよい場合があります。

10-4. SortedDictionaryとOrderByの違いは?

SortedDictionaryは、コレクション自体が常にキー順で管理されます。

一方、OrderByは、その時点のデータを一時的に並び替えるLINQメソッドです。

C#
var dictionary = new Dictionary<int, string>
{
{ 3, "C" },
{ 1, "A" },
{ 2, "B" }
};

var ordered = dictionary.OrderBy(x => x.Key);

キー順で何度も処理するならSortedDictionary、必要なときだけ並び替えればよいならOrderByが向いています。

10-5. SortedDictionaryは大量データに向いている?

SortedDictionaryは検索・追加・削除がO(log n)で行えるため、キー順を維持しながらデータを扱う用途に向いています。

ただし、キー順が不要な大量データならDictionaryの方が適していることが多いです。また、データを一括で作成して読み取り中心で使うなら、SortedListも選択肢になります。

大量データでは、キー順が本当に必要か、追加・削除がどれくらい発生するか、値でソートする必要があるかを考えて選びましょう。

10-6. SortedDictionaryはスレッドセーフ?

SortedDictionaryは、複数スレッドからの読み書きをそのまま安全に行えるコレクションではありません。

Microsoft公式ドキュメントでは、複数の読み取りはコレクションが変更されない限り可能ですが、列挙処理は本質的にスレッドセーフではなく、読み書きを複数スレッドから行う場合は独自に同期処理を実装する必要があると説明されています。Microsoft Learn

複数スレッドからアクセスする場合は、lockを使うなどの対策が必要です。

C#
private readonly object _lock = new object();
private readonly SortedDictionary<int, string> _dictionary = new();

void AddItem(int key, string value)
{
lock (_lock)
{
_dictionary[key] = value;
}
}

string? GetItem(int key)
{
lock (_lock)
{
return _dictionary.TryGetValue(key, out var value) ? value : null;
}
}

まとめ

SortedDictionary<TKey, TValue>は、キーと値を管理しながら、キー順に自動で並び替えてくれるC#の便利なコレクションです。

Dictionaryとの違いは、キー順で管理されるかどうかです。キー順が不要ならDictionary、キー順が必要ならSortedDictionaryを検討するとよいでしょう。

また、SortedListとの違いも重要です。どちらもキー順で管理できますが、SortedDictionaryは追加・削除が多い場合に向いており、SortedListは読み取り中心でメモリ使用量を抑えたい場合に向いています。

選び方を簡単にまとめると、次のようになります。

用途おすすめ
キー順が不要Dictionary
キー順が必要で追加・削除が多いSortedDictionary
キー順が必要で参照中心SortedList
値で並び替えたいLINQのOrderBy
独自の順序で並べたいIComparer付きSortedDictionary

SortedDictionaryを使うときは、キーの重複ができないこと、存在しないキーの参照で例外が発生すること、並び順は値ではなくキーで決まることに注意しましょう。

C#で「キー順に管理できるDictionary」を使いたい場合、SortedDictionaryは非常に有力な選択肢です。用途に応じてDictionarySortedListと使い分けることで、読みやすく効率的なコードを書けるようになります。