| ■No82668 (魔界の仮面弁士) に追記 > たとえば HashSet というクラスは「重複しない要素」を管理するコレクションであり、 > 同じデータを Add しても無視される仕様として設計されています。 > > GetHashCode が一致していた場合にのみ、さらに Equals メソッドで > 追加判定されます。これにより、毎回 Equals を呼び出すことなく、 > 高速に同一データの存在を確認できるようになっています。
以下、HashSet クラスを交えた具体例です。
この一致性判定は、HashSet クラスの他、List.Contains メソッドの要素チェックや、 Dictionary のキーとしても利用されています。
◆パターンA◆
'HashSet.Add メソッドを 2 回呼び出しています。 ' '1 回目の Add と 2 回目の Add とで同一のインスタンスを '指定していますが、Add 前に値を変更しています。 ' Dim o As New test() Dim hs1 As New HashSet(Of test) o.aaa = "1" hs1.Add(o) o.aaa = "2" hs1.Add(o) Console.WriteLine(hs1.Count) '★
Dim p As New System.Net.IPAddress(New Byte() {192, 0, 2, 1}) Dim hs2 As New HashSet(Of System.Net.IPAddress)() hs2.Add(p) p.Address += 1 hs2.Add(p) Console.WriteLine(hs2.Count)
Add された時に、GetHashCode 値が取得され、コレクション内に記録されます。
もし、GetHashCode をオーバーライドしていなかった場合、 o.aaa を書き換えても GetHashCode は変化しません。このため、 登録済みデータに合致する情報であるとみなされ、 2 回目の Add は実施されません。
そのため、★の件数表示で「1」と出力されます。
一方、GetHashCode をオーバーライドして o.aaa の変化に追従させた場合、 別データと看做されるため、両方とも登録され「2」と出力されます。 IPAddress の場合も 2 件ですね。
ただしこの場合、hs(0) と hs(1) が同じオブジェクトへの参照になってしまうため、 『If hs(0) = hs(1) Then』も True になってしまいます。 異なる要素に同じデータが入ってしまうのは、HashSet クラスの特性に向きません。
そのため、このような使い方が必要な場合は、後から内容が書き換えられないよう、 読み取り専用な不変クラス(あるいはそれに準じた実装)とすることを検討してみてください。
◆パターンB◆
'HashSet.Add メソッドを 2 回呼び出しています。 ' '1 回目の Add と 2 回目の Add とで異なるインスタンスを '指定していますが、インスタンス双方は同じ値を有しています。 ' Dim q As New test() With {.aaa = "x", .bbb = "y", .ccc = "z"} Dim r As New test() With {.aaa = "x", .bbb = "y", .ccc = "z"}
Dim hs3 As New HashSet(Of test) hs3.Add(q) hs3.Add(r) Console.WriteLine(hs3.Count) '☆
'(s1 Is s2) は「False」、(s1 = s2)は「True」 Dim sb As New System.Text.StringBuilder("TEST") Dim s1 As String = sb.ToString() Dim s2 As String = sb.ToString()
Dim hs4 As New HashSet(Of String) hs4.Add(s1) hs4.Add(s2) Console.WriteLine(hs4.Count)
このケースでは、☆ は「2」です。 もし、☆を「1」にしたい場合は、test クラスにさらに
Public Overrides Function Equals(obj As Object) As Boolean Return TypeOf obj Is test AndAlso DirectCast(obj, test) = Me End Function
のように、Object.Equals をオーバライドすれば OK です。 Equals の既定の動作は参照性一致なので、「値の比較」にしたい場合は、 その動作を上書きする必要があるわけです。
HashSet について言えば、Object.Equls の代わりに IEquatable(Of ).Equals でも構いません。 Object.Equals と IEquatable(Of ).Equals の両方が実装されていた場合、 IEquatable(Of ) の方が優先されるようになっています。
Public Class test Implements IEquatable(Of test) Private Overloads Function Equals(other As test) As Boolean Implements IEquatable(Of test).Equals Return TypeOf other Is test AndAlso DirectCast(other, test) = Me End Function ' ' ' End Class
◆ Equals メソッド が True を返す場合、その 2 つのオブジェクトの GetHashCode メソッドは、必ず同じ値を返すように実装されていなければなりません。 すなわち Equals を実装する際には、必然的に GetHashCode の実装も必要ということです。
◆ 2 つのオブジェクトの GetHashCode メソッドが同じ値を返したからといって 必ずしも Equals メソッドが True になるとは限りません。 極端な話、GetHashCode を「Return 固定値」で実装しても動作はします。 (探索回数が増えるので、パフォーマンスは犠牲になりますが)
Equals をオーバーライドしたときに、等価演算子まで用意するかどうかは任意です。 しかし逆に等価演算子を実装した場合には、一緒に Equals メソッド (それと GetHashCode) もオーバーライドした方が良いとおもいます。 先の例では、Equals を書き漏れていましたが…。 |