C# ジェネリックコレクションの使い分け

.NET には多数のジェネリックコレクションが用意されています。それぞれ特性が異なるため、用途に応じて使い分けることが重要です。

主要なジェネリックコレクション

List<T>

可変長の配列。インデックスによる高速アクセス。最も汎用的。

Dictionary<TKey, TValue>

キーと値のペア。キーによる高速検索。

HashSet<T>

重複のない要素の集合。存在確認が高速。

Queue<T>

先入れ先出し(FIFO)。順番に処理するタスク向け。

Stack<T>

後入れ先出し(LIFO)。履歴管理や再帰の代替に。

LinkedList<T>

双方向リンクリスト。途中への挿入・削除が高速。

List<T> vs 配列

List<T> は可変長、配列は固定長という違いがあります。

// 配列:サイズ固定
int[] array = new int[3];
// array に要素を追加することはできない

// List&lt;T&gt;:サイズ可変
var list = new List&lt;int&gt; { 1, 2, 3 };
list.Add(4); // 要素の追加が可能

Dictionary<TKey, TValue> の使いどころ

キーから値を高速に検索したい場合に最適です。

var scores = new Dictionary&lt;string, int&gt;
{
    ["Alice"] = 95,
    ["Bob"] = 87,
    ["Charlie"] = 92
};

// O(1) で検索
if (scores.TryGetValue("Alice", out int score))
{
    Console.WriteLine($"Alice: {score}点");
}

HashSet<T> vs List<T>

重複を許さない、または存在確認が多い場合は HashSet<T> が適しています。

List<T> の Contains

O(n) で要素を順番に探す

HashSet<T> の Contains

O(1) でハッシュにより即座に判定

var hashSet = new HashSet&lt;int&gt; { 1, 2, 3, 4, 5 };
var list = new List&lt;int&gt; { 1, 2, 3, 4, 5 };

// 大量のデータで Contains を繰り返す場合、HashSet が圧倒的に速い
bool existsInSet = hashSet.Contains(3);  // 高速
bool existsInList = list.Contains(3);     // 要素数に比例

Queue<T> と Stack<T>

データの処理順序が重要な場合に使います。

// Queue:先に入れたものから出る
var queue = new Queue&lt;string&gt;();
queue.Enqueue("First");
queue.Enqueue("Second");
Console.WriteLine(queue.Dequeue()); // First

// Stack:後に入れたものから出る
var stack = new Stack&lt;string&gt;();
stack.Push("First");
stack.Push("Second");
Console.WriteLine(stack.Pop()); // Second

選択の指針

// インデックスアクセスが必要 → List&lt;T&gt;
var items = new List&lt;string&gt;();
string first = items[0];

// キーで検索したい → Dictionary
var dict = new Dictionary&lt;string, User&gt;();
User user = dict["user123"];

// 重複を排除したい → HashSet
var unique = new HashSet&lt;int&gt;(duplicatedList);

// 順番に処理したい → Queue
var tasks = new Queue&lt;Task&gt;();

// 直前の状態に戻りたい → Stack
var history = new Stack&lt;State&gt;();

パフォーマンス比較

操作 List<T> Dictionary HashSet
インデックスアクセス O(1) - -
末尾への追加 O(1)* O(1)* O(1)*
検索 O(n) O(1) O(1)
削除(値指定) O(n) O(1) O(1)

* 平均的なケース。容量拡張時は O(n)。

用途に適したコレクションを選ぶことで、コードの可読性とパフォーマンスの両方を向上させられます。