使うときは、ソースの末尾などに下の基本版または完全版のコードを追加してから
Dim d As New Dic3(Of Integer, Integer, String)("デフォルト値")
として d の宣言と初期化を行う。 こう定義しておくと、コード中で
d(1, 2) ="文字列"
と書いて、二次元配列のように使える。あらかじめ添え字(キー)の最大値を指定する必要がない。つまり、一度 Dic3 クラスのオブジェクトを作成すれば、あとは何も気にせずに可変長の二次元配列として使えるのがポイント。
【注意】 d(1)(2) ではなく d(1,2) と書くこと
添え字(キー)と値の型は Integer, String 以外も指定できる。
解説
使うときは、下記の(1)か(2)いずれかの形式でオブジェクト d の宣言と初期化を行う。初期化時の "Of Integer, Integer, String" の部分で「最初のキー」「2番目のキー」「値」の型をそれぞれ指定している。(1) デフォルト値を指定する場合
Dim d As New Dic3(Of Integer, Integer, String)("デフォルト値")のようにコンストラクタでデフォルト値を指定しておくと、存在しないキーを参照したときに、 例外 KeyNotFoundException を投げるかわりにデフォルト値を返す。
(2) デフォルト値を指定しない場合
Dim d As New Dic3(Of Integer, Integer, String)()のように初期値を指定しなかった場合は、存在しないキーを参照したときに、通常通り例外 KeyNotFoundException を投げる。
こうして作成したオブジェクト d は d(1, 2) = "ABC" のように、二つの添え字を持つ二次元配列のように使える。引数と値の型は自由に指定できて、例えば上記の宣言時に "
Dim d As New Dic3(Of String, String, String)"と指定すると d("X","Y")="ABC" のように引数も文字列型にできる。
補足
今回作成した Dic3 クラスは 「Dictionary の Dictionary」を継承している。またVB.NET の機能「デフォルトのプロパティ」を使って(item メソッドを書き換えて)二次元配列のように、カッコ内に2個の引数をとって使えるようにしている。通常、「Dictionary の Dictionary」 では、代入する時に 2番目の Dictionary オブジェクトの初期化が必要になるが、今回は新しいキーが現れると、2番目の Dictionary のメンバが New されるようにしてあるので、 2番目の Dictionary を明示的に初期化しなくてもよい。
普通は基本版を使えばよい。完全版は基本版にコンストラクタなどを追加したもの。完全版の方はオブジェクトのバイナリでのシリアライズができる。
基本版
' ---------------- このクラスをコードの末尾に挿入して定義してください ' Dic3: 2個のキーを持つ Dictionary コンテナ ' USAGE: dim d as new Dic3(of キー1の型, キー2の型, 値の型)(デフォルト値) ' ' 使用例: ' Dim d As New Dic3(Of Integer, Integer, String)("デフォルト") ' d(0,0) = "文字列" ' 初期化せずいきなり代入可能 Public Class Dic3(Of TK1, TK2, TV) Inherits Dictionary(Of TK1, Dictionary(Of TK2, TV)) Public DefaultValue As TV = Nothing ' --- 追加のコンストラクタ Public Sub New() End Sub Public Sub New(ByVal def As TV) DefaultValue = def End Sub ' --- 引数1個の Item() メソッド Default Shadows Property item(ByVal a1 As TK1) As Dictionary(Of TK2, TV) Get If (Not Me.ContainsKey(a1)) Then MyBase.Item(a1) = New Dictionary(Of TK2, TV) Return MyBase.Item(a1) End Get Set(ByVal value As Dictionary(Of TK2, TV)) MyBase.Item(a1) = value End Set End Property ' --- 引数2個の Item() メソッド Default Shadows Property item(ByVal a1 As TK1, ByVal a2 As TK2) As TV <DebuggerHidden()> Get If (Not Me.ContainsKey(a1) And DefaultValue IsNot Nothing) Then MyBase.Item(a1) = New Dictionary(Of TK2, TV) Return DefaultValue End If If (Not MyBase.Item(a1).ContainsKey(a2) And DefaultValue IsNot Nothing) Then Return DefaultValue Return MyBase.Item(a1)(a2) End Get Set(ByVal Value As TV) If (Not Me.ContainsKey(a1)) Then MyBase.Item(a1) = New Dictionary(Of TK2, TV) MyBase.Item(a1)(a2) = Value End Set End Property End Class
コンストラクタを追加した完全版
'---------------- このクラスをコードの末尾に挿入して定義してください ' Dic3: 2個のキーを持つ Dictionary コンテナ ' USAGE: dim d as new Dic3(of キー1の型, キー2の型, 値の型)(デフォルト値) ' ' 使用例: ' Dim d As New Dic3(Of Integer, Integer, String)("デフォルト") ' d(0,0) = "文字列" ' 初期化せずいきなり代入可能 <Serializable()> Public Class Dic3(Of TK1, TK2, TV) Inherits Dictionary(Of TK1, Dictionary(Of TK2, TV)) Public DefaultValue As TV = Nothing ' --- 基底クラスのコンストラクタ Public Sub New(ByRef a1 As Dictionary(Of TK1, Dictionary(Of TK2, TV))) MyBase.New(a1) End Sub Public Sub New(ByRef a1 As IEqualityComparer(Of TK1)) MyBase.New(a1) End Sub 'Public Sub New(ByRef a1 As Int32) ' MyBase.New(a1) 'End Sub Public Sub New(ByRef a1 As IDictionary(Of TK1, TV), ByRef a2 As IEqualityComparer(Of TK1)) MyBase.New(a1, a2) End Sub Public Sub New(ByRef a1 As Int32, ByRef a2 As IEqualityComparer(Of TK1)) MyBase.New(a1, a2) End Sub '---- シリアライズ Private Sub New(ByVal info As System.Runtime.Serialization.SerializationInfo, _ ByVal context As System.Runtime.Serialization.StreamingContext) MyBase.New(info, context) DefaultValue = info.GetValue("DEFAULT", GetType(TV)) ' 追加したメンバ End Sub Public Overrides Sub GetObjectData(ByVal info As System.Runtime.Serialization.SerializationInfo, _ ByVal context As System.Runtime.Serialization.StreamingContext) MyBase.GetObjectData(info, context) info.AddValue("DEFAULT", DefaultValue) ' 追加したメンバ End Sub ' --- 追加のコンストラクタ Public Sub New() End Sub Public Sub New(ByVal def As TV) DefaultValue = def End Sub ' --- 引数1個の Item() メソッド Default Shadows Property item(ByVal a1 As TK1) As Dictionary(Of TK2, TV) Get If (Not Me.ContainsKey(a1)) Then MyBase.Item(a1) = New Dictionary(Of TK2, TV) Return MyBase.Item(a1) End Get Set(ByVal value As Dictionary(Of TK2, TV)) MyBase.Item(a1) = value End Set End Property ' --- 引数2個の Item() メソッド Default Shadows Property item(ByVal a1 As TK1, ByVal a2 As TK2) As TV <DebuggerHidden()> Get If (Not Me.ContainsKey(a1) And DefaultValue IsNot Nothing) Then MyBase.Item(a1) = New Dictionary(Of TK2, TV) Return DefaultValue End If If (Not MyBase.Item(a1).ContainsKey(a2) And DefaultValue IsNot Nothing) Then Return DefaultValue Return MyBase.Item(a1)(a2) End Get Set(ByVal Value As TV) If (Not Me.ContainsKey(a1)) Then MyBase.Item(a1) = New Dictionary(Of TK2, TV) MyBase.Item(a1)(a2) = Value End Set End Property End Class
完全版の方の Dic3 オブジェクトなら、下のページの方が紹介している VB.NET の方法で、2次元配列を丸ごと複製したオブジェクトが作れます。
返信削除「どんなオブジェクトでもコピーできる汎用のディープコピー処理」
http://d.hatena.ne.jp/tekk/20100131/1264913887