HashSet<T>とは何か?
C#のSystem.Collections.Generic名前空間には、List<T>やDictionary<TKey, TValue>といった便利なコレクションが用意されています。その中でもHashSet<T>は、ユニークな(重複しない)要素を管理することに特化したコレクションです。
HashSet<T>の主な特徴は以下の2点です。
- 重複の自動的な排除: 同じ要素を2回
Add(追加)しようとしても、無視されます(エラーにはなりません)。HashSet<T>内には、常に一意な要素だけが格納されます。 - 非常に高速な検索:
Dictionaryがキーを高速に検索するのと同様に、HashSet<T>はContainsメソッド(要素が含まれているか)の実行が非常に高速です。List<T>.Containsがリストの先頭から順に探す(要素数に比例して遅くなる)のに対し、HashSet<T>.Containsは要素数に関わらずほぼ一定の速度で動作します。
一方で、List<T>とは異なり、HashSet<T>は要素の順序を保証しません(追加した順序で保持されません)。
HashSet<T>の基本的な使い方
HashSet<T>は、タグの管理、重複のないIDリストの作成、既知のアイテムの高速なチェックなどに最適です。
1. 初期化と Add (重複の無視)
new HashSet<T>()でインスタンス化します。コレクション初期化子({ ... })で初期値を設定することもできます。
Addメソッドは、要素の追加を試み、bool値(追加に成功したか=まだ存在しなかったか)を返します。
using System;
using System.Collections.Generic; // HashSet<T> を使うために必要
public class HashSetAddExample
{
public static void Main()
{
// 1. HashSet<string> の初期化
var uniqueTags = new HashSet<string>
{
"C#", "Programming", ".NET"
};
Console.WriteLine($"--- 初期状態 ({uniqueTags.Count}件) ---");
PrintSet(uniqueTags);
// 2. 新しい要素の追加 (成功する)
bool addedNew = uniqueTags.Add("LINQ");
Console.WriteLine($"\n\"LINQ\" の追加成功: {addedNew} (True)");
// 3. 重複した要素の追加 (無視される)
bool addedDuplicate = uniqueTags.Add("C#");
Console.WriteLine($"\"C#\" の追加成功: {addedDuplicate} (False)");
Console.WriteLine($"\n--- 追加後の状態 ({uniqueTags.Count}件) ---");
PrintSet(uniqueTags); // "C#" は1つしか存在しない
}
// HashSet の内容を出力するヘルパーメソッド
private static void PrintSet(HashSet<string> set)
{
Console.WriteLine(string.Join(", ", set));
}
}
出力結果(順序は保証されません):
--- 初期状態 (3件) ---
C#, Programming, .NET
"LINQ" の追加成功: True
"C#" の追加成功: False
--- 追加後の状態 (4件) ---
C#, Programming, .NET, LINQ
2. Contains (高速な存在確認)
HashSet<T>の最大の強みは、Containsメソッドによる高速な存在確認です。
bool hasDotNet = uniqueTags.Contains(".NET"); // True
bool hasJava = uniqueTags.Contains("Java"); // False
Console.WriteLine($"'.NET' は存在するか: {hasDotNet}");
Console.WriteLine($"'Java' は存在するか: {hasJava}");
3. Remove (要素の削除)
Removeメソッドは、指定した要素がHashSet<T>内に存在すれば削除し、true(削除成功)を返します。存在しなければ何もせずfalseを返します。
// "Programming" を削除
bool removed = uniqueTags.Remove("Programming");
Console.WriteLine($"\n\"Programming\" の削除成功: {removed} (True)");
Console.WriteLine($"\n--- 削除後の状態 ({uniqueTags.Count}件) ---");
PrintSet(uniqueTags); // "Programming" が消えている
4. foreach による反復処理
HashSet<T>はforeachループで全要素を処理できますが、List<T>とは異なり、要素が取り出される順序は保証されない(追加した順序とは限らない)ことに注意が必要です。
C#コード全体(まとめ)
using System;
using System.Collections.Generic;
public class HashSetUsageExample
{
public static void Main()
{
// 1. HashSet の初期化 (重複は自動的に無視される)
var uniqueTags = new HashSet<string>
{
"C#", "Programming", ".NET", "C#" // "C#" は1つだけ格納される
};
Console.WriteLine("--- 初期状態 ---");
PrintSet(uniqueTags); // 3件
// 2. Add: 重複の試行
bool addedDuplicate = uniqueTags.Add(".NET");
Console.WriteLine($"\n'.NET' の追加試行: {addedDuplicate} (False)");
// 3. Count: 要素数の取得
Console.WriteLine($"現在の要素数: {uniqueTags.Count}");
// 4. Contains: 高速な存在確認
bool hasCSharp = uniqueTags.Contains("C#");
Console.WriteLine($"'C#' は存在するか: {hasCSharp} (True)");
// 5. Remove: 要素の削除
uniqueTags.Remove("Programming");
Console.WriteLine($"\n'Programming' を削除しました。");
// 6. foreach: 反復処理 (順序は保証されない)
Console.WriteLine("\n--- 最終的な要素一覧 ---");
foreach (var tag in uniqueTags)
{
Console.WriteLine(tag);
}
}
// HashSet の内容を出力するヘルパーメソッド
private static void PrintSet(HashSet<string> set)
{
Console.WriteLine(string.Join(", ", set));
}
}
出力結果(順序は異なる場合があります):
--- 初期状態 ---
C#, Programming, .NET
'.NET' の追加試行: False
現在の要素数: 3
'C#' は存在するか: True (True)
'Programming' を削除しました。
--- 最終的な要素一覧 ---
C#
.NET
まとめ
HashSet<T>は、List<T>や配列とは異なる目的を持つコレクションです。
List<T>: 順序が重要で、重複を許可する場合。(例: 履歴、スコアのリスト)HashSet<T>: 順序は重要ではなく、重複を排除したい場合、または「その要素が存在するかどうか」を高速に判定したい場合。(例: タグ、固有IDの管理)
