【テクニカル・上級編】【配列の完全複製(ディープコピー)】参照渡しによるデータ破壊を防ぐ多次元配列・連想配列のクローン処理 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

VBScriptにおける「不変性」の追求:多次元配列と連想配列の完全複製(ディープコピー)を掌握する

Windowsシステム管理、あるいはレガシーなエンタープライズシステムの保守において、VBScriptは今なお「静かなる守護神」である。しかし、この言語を真に制御できている者は驚くほど少ない。

特に、大規模なデータを扱う際に避けて通れないのが「参照渡し(ByRef)」によるデータ破壊の罠だ。VBScriptのVariant型は、その柔軟性の代償として、開発者が意図しないタイミングで元のデータを書き換えてしまう脆弱性を孕んでいる。

今回は、プロフェッショナルの現場で必須となる、多次元配列および`Scripting.Dictionary`を完全に独立した別インスタンスとして複製する「ディープコピー」の極致を解説する。

1. 参照という名の「時限爆弾」

VBScriptにおいて、オブジェクト(Dictionary等)を代入する際、`Set`ステートメントはインスタンスへのポインタのみをコピーする。配列についても、一見値がコピーされているように見えて、その要素がオブジェクトであれば、参照が引き継がれる。

これが何を意味するか。ある関数内で加工した一時的なデータが、呼び出し元のマスターデータを汚染し、原因不明のバグを誘発するということだ。これを防ぐには、メモリ上の全く別の領域に構造を再構築する「ディープコピー」が不可欠となる。

2. 深淵なる再帰:ディープコピー・エンジンの設計

多次元配列や、入れ子になったDictionaryを完全に複製するには、再帰的な探索が必要だ。以下に、私が長年のミッションクリティカルな現場で洗練させてきた、汎用ディープコピー関数 `DeepCopy` を提示する。

このコードは、単なるコピーではない。各要素の型を判別し、オブジェクトであれば再帰的に潜り、配列であればその次元を維持したままクローンを生成する。


‘ 関数名:DeepCopyAny
‘ 概要 :配列、Dictionary、プリミティブ型を問わず、完全に独立した複製を生成する。
‘ 引数 :vSource – 複製元データ
‘ 戻り値:複製された独立したデータ

Function DeepCopyAny(ByVal vSource)
If IsObject(vSource) Then
If TypeName(vSource) = “Dictionary” Then
‘ — Scripting.Dictionary のディープコピー —
Dim dictSource, dictClone, key
Set dictSource = vSource
Set dictClone = CreateObject(“Scripting.Dictionary”)

‘ 比較モード(バイナリ/テキスト)の継承
dictClone.CompareMode = dictSource.CompareMode

For Each key In dictSource.Keys
‘ キー自体がオブジェクトである可能性も考慮し、再帰的にコピー
‘ (通常は文字列だが、堅牢性を担保)
If IsObject(dictSource.Item(key)) Then
Set dictClone.Item(key) = DeepCopyAny(dictSource.Item(key))
Else
dictClone.Item(key) = DeepCopyAny(dictSource.Item(key))
End If
Next
Set DeepCopyAny = dictClone
Set dictClone = Nothing ‘ 明示的解放
Else
‘ その他のオブジェクト(カスタムクラス等)
‘ VBScriptでは汎用的なCloneメソッドを持たないため、参照を返すか
‘ 固有の複製ロジックをここに実装する
Set DeepCopyAny = vSource
End If
ElseIf IsArray(vSource) Then
‘ — 配列のディープコピー(多次元対応) —
DeepCopyAny = CloneArray(vSource)
Else
‘ — プリミティブ型(数値、文字列、日付等) —
DeepCopyAny = vSource
End If
End Function


‘ 関数名:CloneArray
‘ 概要 :多次元配列を再帰的にスキャンし、要素レベルでディープコピーを行う。

Function CloneArray(ByVal arrSource)
Dim arrClone
Dim i, j, dims

‘ VBScriptの配列次元数を取得するトリッキーだが確実な方法
dims = GetArrayDimensions(arrSource)

Select Case dims
Case 1
ReDim arrClone(UBound(arrSource))
For i = LBound(arrSource) To UBound(arrSource)
If IsObject(arrSource(i)) Then
Set arrClone(i) = DeepCopyAny(arrSource(i))
Else
arrClone(i) = DeepCopyAny(arrSource(i))
End If
Next
Case 2
ReDim arrClone(UBound(arrSource, 1), UBound(arrSource, 2))
For i = LBound(arrSource, 1) To UBound(arrSource, 1)
For j = LBound(arrSource, 2) To UBound(arrSource, 2)
If IsObject(arrSource(i, j)) Then
Set arrClone(i, j) = DeepCopyAny(arrSource(i, j))
Else
arrClone(i, j) = DeepCopyAny(arrSource(i, j))
End If
Next
Next
Case Else
‘ 3次元以上の配列が必要な場合は、動的再帰ロジックを拡張するが、
‘ 業務自動化において2次元を超える設計は保守性を著しく下げるため推奨しない。
Err.Raise 5, “CloneArray”, “Unsupported array dimensions: ” & dims
End Select

CloneArray = arrClone
End Function


‘ 補助関数:配列の次元数を判定する

Function GetArrayDimensions(ByVal arr)
Dim i, tmp
On Error Resume Next
For i = 1 To 60 ‘ WinAPIのSAFEARRAY構造体上の制限に準拠
tmp = UBound(arr, i)
If Err.Number <> 0 Then
GetArrayDimensions = i – 1
Err.Clear
Exit Function
End If
Next
End Function

3. メモリ最適化とパフォーマンスの視点

VBScriptはCOM(Component Object Model)の上に成り立っている。`Scripting.Dictionary` や `ADODB.Recordset` などのCOMオブジェクトを大量に生成・コピーする場合、メモリの断片化とリソースの枯渇に細心の注意を払わなければならない。

明示的な解放の重要性

上記のコード内で `Set dictClone = Nothing` を行っているのは、単なるマナーではない。VBScriptのガベージコレクション(参照カウント方式)において、循環参照が発生した場合や、巨大な構造体を扱う場合、スコープを抜けるまでメモリが解放されないことがシステムの致命傷になるからだ。

SAFEARRAYのメモリ構造

VBScriptの配列は内部的に `SAFEARRAY` 構造体として管理されている。`arr2 = arr1` と記述した際、VBScriptは要素がプリミティブ型であれば自動的に値のコピーを行う。しかし、要素が `Variant` 型で、その中身が `IDispatch` インターフェース(オブジェクト)を指している場合、コピーされるのはポインタのみだ。これが、多次元配列においてディープコピーが必要とされる真の理由である。

4. 実戦での活用例:設定情報の保護

例えば、システム全体の共通設定を保持する `MasterConfig`(Dictionary)があるとする。特定の処理モジュールでその一部を書き換えてシミュレーションを行いたい場合、ディープコピーなしでは `MasterConfig` そのものが書き換わってしまう。

Dim MasterConfig, WorkingConfig
Set MasterConfig = CreateObject(“Scripting.Dictionary”)
MasterConfig.Add “API_URL”, “https://api.example.com”
MasterConfig.Add “RetryLimit”, 3

‘ 悪い例:参照渡し
‘ Set WorkingConfig = MasterConfig

‘ 良い例:ディープコピー
Set WorkingConfig = DeepCopyAny(MasterConfig)

‘ 作業用設定を変更しても、MasterConfigは汚染されない
WorkingConfig.Item(“RetryLimit”) = 10

WScript.Echo “Master: ” & MasterConfig.Item(“RetryLimit”) ‘ 3 を維持
WScript.Echo “Working: ” & WorkingConfig.Item(“RetryLimit”) ‘ 10

5. アーキテクトの結論

VBScriptは古い言語だ。しかし、その根底にあるCOMの仕組みとメモリ管理の原則を理解すれば、これほど堅牢で制御しやすい環境もない。

データの「参照」と「実体」を峻別できないエンジニアは、いつか必ず大規模障害を引き起こす。今回紹介したディープコピーのロジックは、単なるコード片ではない。それは、データの整合性を死守し、予測可能なシステムを構築するための「哲学」そのものである。

保守フェーズにあるレガシーシステムを預かる諸君、あるいはWSHで高度な自動化を試みる諸君。この「不変性」へのアプローチを、自らのツールボックスに深く刻み込んでほしい。

タイトルとURLをコピーしました