2013年6月9日日曜日

VB.NET で2個のキーを持つDictionaryクラスを作る

VB.NET で複数のキーを持つ Dictionary が使いたかったので、新しく Dic3 クラスを作成した。動作確認は VB.NET 2008 で行った。VB のバージョンが古いと動作しないかもしれない。

使うときは、ソースの末尾などに下の基本版または完全版のコードを追加してから
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 を投げる。

こうして作成したオブジェクト dd(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

1 件のコメント:

  1. 完全版の方の Dic3 オブジェクトなら、下のページの方が紹介している VB.NET の方法で、2次元配列を丸ごと複製したオブジェクトが作れます。
    「どんなオブジェクトでもコピーできる汎用のディープコピー処理」
    http://d.hatena.ne.jp/tekk/20100131/1264913887

    返信削除