Variant とユーザ定義型

 VB は基本的にすべての値を Variant で扱おうとする言語です。
型に厳しいのかどうかが言語の優劣を決めるわけではないのですが、現在の VB は型を意識した機能と Variant を意識した機能が入り混じっており、結果としてプログラム言語として扱いにくいかんじがします。
昨日の続きの話になるのですが、動的配列に要素を足していく処理を修正しているうちに、この処理の元になるデータ処理を再帰にしたくなりました。もともとのコードは、

For i=0 To ...
  if ... Then
    ' ary() に要素を追加       …(A)
    If hasChild Then
      ' ary() に子要素を追加
      For j=0 To ...
        If ... Then
          ' 内容的には (A) と同じ
    End If
  End If
Next i

のように二重のループをもちいて子要素をサポートしていたのですが、追加仕様では孫要素を処理できる必要がありました。
そのうち、曾孫要素や、曾孫の子*1要素の処理も必要になる可能性がありますので、ここは再帰を利用するように変更しておくほうが仕様としては良いと思ったのです。

Private Sub AddElement(ByRef ary() as MyType, ...)
  If ... Then
    ' ary() に要素を追加する
    If hasChild Then AddElement(ary(), ...)
  End If
End Sub

単純ですが、コンパイルに問題はないものの実行時にインデックスが正しくないというエラーがでました。どうも、要素を追加する処理に問題があるようです。要素を追加する処理に変更の余地があることは昨日の日記にあるとおりですが、この時点では

ReDim Preserve ary(0 To UBound(ary)+1)

というコードを毎回実行するようになっていましたが、1度も ReDim を実行していない動的配列に対して LBound や UBound を実行すると、問題のエラーが発生するということなのです。デバッガで変数の内容を覗くと Nothing と表示されるます。
この問題は Google で検索するとすぐに解決策になりそうな情報を得ることができ、ary を空の配列で初期化しておけば LBound は 0 を、UBound は -1 を返すので、安全に ReDim を実行できるということがわかりました。
しかし、問題は空の配列の作成方法で、VB6 の機能では基本型と Variant 型に関しては可能であるが、ユーザ定義型に関してはユーザ定義型毎の空の配列を返す ActiveX を別言語を利用して作成する必要があるということです。型に依存しない方法としてエラーハンドリングを行えばよいとあったので、

  On Error Goto ary_CreateNew
  ReDim Preseve ary(0 To UBound(ary)+1)
  On Error Goto 0
   :
ary_CreateNew:
  If Err.Number=9 Then
    ReDim ary(0 To 1)
    Resume Next
  Else
    Err.Raise Err.Number, ...
  End If

のようになりました。
また、後でわかったことですが、Collection の要素や、クラスモジュール内のルーチンの戻り値や引数にもユーザ定義型を利用することができないようです。

*1:孫の子を曾孫と呼ぶように、曾孫の子のことを示す語もあるのかな?