はじめに
連想配列ってあるじゃないですか。
.NETだとSystem.Collections.Generic.Dictionary(Of TKey, TValue)ですね。*1
Dictionaryはキーに複数のインスタンスを指定できないので、複数のキーを指定したいときはそれらを含みさらに適切に実装したクラスを実装しないといけません。
たとえば、2つのIntegerをキーとするために以下のクラスを実装したとします。
Class MyKey
Public Property Key1 As Integer
Public Property Key2 As Integer
Public Sub New(key1 As Integer, key2 As Integer)
Me.Key1 = key1
Me.Key2 = key2
End Sub
End Class
こんなコードを書いても、絶対の格納した値を取り出すことはできません。
Sub Main() Dim dic = New Dictionary(Of MyKey, String) dic.Add(New MyKey(1, 1), "Hoge") Console.WriteLine(dic(New MyKey(1, 1))) End Sub
当たり前です。Object.Equalsをオーバーライドしていないため、以下のコードがFalseを返すためです。
Dim k1 = New MyKey(1, 1) Dim k2 = New MyKey(1, 1) Console.WriteLine(k1.Equals(k2))
Dictionaryはキーの等値にObject.Equalsを利用しており、Object.Equalsはオーバーライドしなければ参照が等しいかどうかの値を返します。
つまり、内容の等しさでEqualsがTrueを返すようにオーバーライドする必要があります。
また、Dictionaryのようなハッシュを用いるクラスで使うためにはObject.GetHashCodeも実装する必要があります。*2
さらにさらに注意が必要な点として、ハッシュを計算するために用いる値は変更して(出来て)はいけません。 現実的に考えるとイミュータブルなクラスとして設計する必要があります。
あとはSystem.IEquatable(Of T)を実装したりと、もはや自分が何をしたいのかを忘れてしまいそうです。
とまぁ、以上の事柄を実装するとこんな感じに以下略
Class MyKey2
Implements System.IEquatable(Of MyKey2)
Private _key1 As Integer
Private _key2 As Integer
Public ReadOnly Property Key1 As Integer
Get
Return _key1
End Get
End Property
Public ReadOnly Property Key2 As Integer
Get
Return _key2
End Get
End Property
Public Sub New(ByVal key1 As Integer, ByVal key2 As Integer)
_key1 = key1
_key2 = key2
End Sub
Public Overrides Function Equals(obj As Object) As Boolean
Dim other = TryCast(obj, MyKey2)
If other Is Nothing Then
Return False
Else
Return DirectCast(Me, IEquatable(Of MyKey2)).Equals(other)
End If
End Function
Public Overrides Function GetHashCode() As Integer
Return _key1 Xor _key2
End Function
Public Function Equals1(other As MyKey2) As Boolean Implements IEquatable(Of MyKey2).Equals
Return Me._key1 = other._key1 AndAlso Me._key2 = other._key2
End Function
End Class
Sub Main() Dim dic = New Dictionary(Of MyKey2, String) dic(New MyKey2(1, 1)) = "Hoge" Console.WriteLine(dic(New MyKey2(1, 1))) End Sub
タプル
こんなクッソ面倒なコードをいちいち書いていたら終わるものも終わりません。 それなら一度汎用的なものを実装して使い回すのが妥当です。 まぁ、すでに用意されているのですが。
それが今回紹介するタプルです。 Haskell や Erlang、Python などを使用している人にとってはとても馴染みが深い概念だと思います。
.NET Framework 4 以上でしか使えませんが、そもそも今時3.5で開発するんか? と言った感じです。
使い方はとっても簡単。 以下のコードでokです。
Sub Main() Dim dic = New Dictionary(Of Tuple(Of Integer, Integer), String) dic(Tuple.Create(1, 1)) = "Hoge" Console.WriteLine(dic(Tuple.Create(1, 1))) End Sub
連想配列のキーだけでなく、クラスを一つ用意するまでもないけどいくつかの情報を束ねてコレクションにブチ込みたい時にも便利です。
この場合、Item1、Item2などのプロパティを介して格納した値にアクセス可能です。
Sub Main() Dim addressbook = New List(Of Tuple(Of String, String)) addressbook.Add(Tuple.Create("Alice", "alice@jyuch.com")) addressbook.Add(Tuple.Create("Bob", "bob@jyuch.com")) For Each it In addressbook Console.WriteLine("{0} {1}", it.Item1, it.Item2) Next End Sub
型推論により厳密に型指定ができ、異なる要素からなるタプルとはコンパイラレベルで区別できます。
そのため、ジェネリックにより型指定されたコレクションクラスなどにおいては異なる要素からなるタプルが混入するといったことは無くなります。
まぁ、一部のVBプログラマが大好きなOption Strict Offと非ジェネリックコレクションの前には無力ですが。
とまぁ、詠唱破棄してクラスっぽいものを使えるTupleは確かに便利ですが、欠点がないわけではありません。
デメリット:わかりずらい
クラス内の限られた範囲ならまだセーフだと思いますが、外部に公開すべきではありません。
たとえば、こんなメソッドがあったとして返り値の各項目が何を表すかは定義だけでは全く分かりません。 タプルが区別するのはあくまでも要素の型で、内容までは知ったこっちゃないです。
Public Function SelectByID(ByVal id As Integer) As Tuple(Of Integer, Tuple(Of String, String), String)
XMLコメントドキュメントで説明するという方法もありますが、その前にコードで表現可能な場合はコードで説明すべきです。
Function SelectByID(ByVal id As Integer) As MyAddress Return Nothing End Function
Class MyAddress
Public Property ID As Integer
Public Property FirstName As String
Public Property LastName As String
Public Property MailAddress As String
End Class
仮に説明されていたとしても、Item1が何を表しているかなんてパッと見ても分かりません。
個人的にはタプルはできればメソッド内、許されるギリギリがクラス内での利用ですね。 クラスの外に出てしまうと何を表しているのかが分かりにくいというデメリットが予想以上に効いてきます。
アセンブリの外に出してしまうのは本当にやめた方がいいです。
注意点:使える型に条件がある
Tupleは等値判定やハッシュをいい感じにアレしてくれますが、Tuple自体は項目として含む値のEqualsやGetHashCodeを利用しているので、その辺をしっかりしていないクラスを要素として含んでしまうと等値判定などがうまくいかなくなります。
Sub Main() Dim t1 = Tuple.Create(New MyKey(1, 1), 1) Dim t2 = Tuple.Create(New MyKey(1, 1), 1) Dim t3 = Tuple.Create(New MyKey2(1, 1), 1) Dim t4 = Tuple.Create(New MyKey2(1, 1), 1) Console.WriteLine(t1.Equals(t2)) ' False Console.WriteLine(t3.Equals(t4)) ' True End Sub
おわりに
とまぁ、メリットよりもデメリットを強調してしまった気がするのですが、Tupleはすごく便利です。
用法・用量を守って正しく使えば分かりにくいというデメリットもあまり感じません。
あと、なんか言ってなかったぽいですがタプルはイミュータブルなのでその辺はいい感じにお願いします。
おわり