えんじにあ雑記!

開発していて学んだことをまとめていきます!

C#で自作クラスをHashSetで正しく動作させる

f:id:flat-M_M:20200407212505j:plain

C#APIであるHashSet

自作クラスで正しく動作させるにはどうしたらいいの?

HashSetとは

HashSetとは値の重複を許可しないセットのことです。

例えば、[1, 2, 3, 3, 4, 5]という6つの数字を格納した配列をHashSetで管理した時、「3」が重複しているので二つ目の「3」はセットには含まれません。

具体的に実装してみたものが以下の通りです。

static void Main(string[] args)
{
    int[] nums = new int[] { 1, 2, 3, 3, 4, 5 };
    Console.WriteLine("-----int[]の場合-----");
    foreach (var num in nums)
    {
        Console.WriteLine(num);
    }
    HashSet<int> numsSet = new HashSet<int> { 1, 2, 3, 3, 4, 5 };
    Console.WriteLine("-----HashSetの場合-----");
    foreach (var num in numsSet)
    {
        Console.WriteLine(num);
    }
}

このコードの実行結果は以下の通りです。

-----int[]の場合-----
1
2
3
3
4
5
-----HashSetの場合-----
1
2
3
4
5

このように重複する「3」という要素がHashSetの場合は1つだけになっているのが分かります。

HashSetの定義などに関する詳細は以下のMicrosoftの公式ドキュメントを参考にしてください。

docs.microsoft.com

HashSetの仕組み

では、HashSetがどうやって重複を検知しているかを説明します。

  1. GetHashCodeメソッドをもとにハッシュ値を比較する
  2. Equalsメソッドでオブジェクトの同値性を調べる

この2ステップを経て、重複かどうかを判定しています。

Objectクラスについて

C#全てのオブジェクトの基底クラスにObjectクラスがあります。そのObjectクラスにはハッシュ値を求めるためのGetHashCode()メソッドと、オブジェクトの同値性を判定するEqualsメソッドが定義されています。また同一性を判定するためのReferenceEqualsメソッドなども定義されています。 Objectクラスについては以下の公式ドキュメントを参考にしてください。

docs.microsoft.com

  1. GetHashCode()をもとにハッシュ値を比較する
  2. Equalsメソッドでオブジェクトの同値性を調べる

とは、

  1. 自作クラスで正しくGetHashCode()をオーバーライドする
  2. 自作クラスで正しくEqualsメソッドをオーバーライドする

ということになります

GetHashCodeをオーバーライド

まず自作クラスとして「ユーザ名」と「ユーザID」を管理するUserクラスを定義します。

public class User
{
    public User(string name, string id)
    {
        Name = name;
        ID = id;
    }

    public string Name { get; private set; }
    public string ID { get; private set; }
}

次にGetHashCode()をオーバーライドしていきます。今回は、「ユーザ名」と「ユーザID」が同一である場合に同じハッシュ値になるように実装します。

public override int GetHashCode()
{
    return Name.GetHashCode() ^ ID.GetHashCode();
}

次にEqualsメソッドもオーバーライドします。

public override bool Equals(object obj)
{
    User user = obj as User;
    if (user == null) return false;
    return Name == user.Name && ID == user.ID;
}

これでHashSetで正しく動作する自作クラスが作成できました。

動作確認

public static void Test()
{
    HashSet<User> users = new HashSet<User>
    {
        new User("John", "0"),
        new User("Mike", "0"),
        new User("Sam", "0"),
        new User("Mike", "0")
    };
    foreach (var user in users)
    {
        Console.WriteLine(user.Name);
    }
}

実行結果は以下の通りになります。

John
Mike
Sam