Excel VBAを掌握する極限の知見:Dictionaryのキーにユーザー定義型(UDT)をねじ込むためのハッシュ化戦略
開発現場でVBAの限界を押し広げようとするとき、`Scripting.Dictionary` は避けて通れない最強の武器だ。配列の線形探索(O(n))という前近代的なアプローチから我々を解放し、ハッシュによるO(1)の高速ルックアップをもたらしてくれる。
だが、ここで一つ、多くのプログラマが直面する絶望的な壁がある。
「複数の条件(例:部署コード + 担当者ID + 対象月)を組み合わせたキーでDictionaryを引きたい。しかし、キーにユーザー定義型(UDT)やオブジェクトは直接指定できない」
標準のDictionaryは、キーとしてオブジェクト参照そのもの(メモリ上のポインタ)を比較するか、単純なスカラー値(文字列や数値)しか受け付けない。UDT(ユーザー定義型)の変数をそのまま `dic.Add myUDT, value` とやろうものなら、Runtime Error 13(型が一致しません)が冷酷に突きつけられる。
今回は、この制約を華麗に突破し、複雑な構造を持つデータを安全かつ爆速でDictionaryのキーとして扱うための「ハッシュ化戦略」を、プロダクション品質のコードとともに伝授する。
—
なぜ「安易な連結」でバグが頻発するのか?
多くの初中級プログラマがやりがちな間違いが、UDTの各要素を単に文字列結合してキーにする手法だ。
‘ 【アンチパターン】単純結合によるキー生成
Type TEmployee
CompanyCode As String
StaffId As Long
End Type
Dim keyStr As String
keyStr = myUDT.CompanyCode & myUDT.StaffId
このアプローチには、実務において致命的な脆弱性が潜んでいる。
1. 境界値の衝突(Delimiter Collision):
`CompanyCode = “A”`, `StaffId = “105”` の結合結果は `”A105″`。
`CompanyCode = “A1″`, `StaffId = “05”` の結合結果も `”A105″`。
全く異なる実体が、同一のキーとして評価され、既存のデータを破壊(上書き)する。
2. 型の暗黙の変換とパフォーマンス:
文字列連結はVBA内部でVariantを介した動的なメモリ確保が発生するため、ループ内で大量に実行するとガベージコレクションの負荷とメモリ断片化を招く。
プロフェッショナルたる者、一意性(Uniqueness)と堅牢性(Robustness)が数学的に保証されたハッシュ文字列を生成せねばならない。
—
決定版:一意性を担保する「構造化シリアライズ&ハッシュ化戦略」
実務で破綻しないための設計方針はこうだ。
1. 固定長・デリミタ付きシリアライズ:各フィールドの型と長さを意識し、絶対に衝突しない区切り文字(または固定長パディング)を挟んで結合する。
2. MD5等のハッシュアルゴリズムの活用(または擬似ハッシュ):文字列が長大になる、あるいはバイナリを含む場合は、Windows環境標準で利用可能なCryptoAPIや、VBA内で完結する高速な文字列ハッシュ関数(FNV-1aハッシュなど)を経由して、固定長のキーに変換する。
今回は、業務システムで最も実用的かつ追加ライブラリ不要な「安全なデリミタ付き構造化キー生成関数」を組み込んだプロダクションコードを提示する。
—
プロダクションコード:UDTをキーにしたDictionaryラッパー
以下のコードは、そのままモジュールに貼り付けて利用できる。UDTをラップし、内部で一意な文字列キーに変換してDictionaryを操作するデザインパターンだ。
Option Explicit
‘ =================================================================
‘ 1. データの構造定義(ユーザー定義型:UDT)
‘ =================================================================
Public Type TOrderKey
StoreCode As String ‘ 店舗コード
CategoryNo As Integer ‘ カテゴリ番号
SalesDate As Date ‘ 売上日
End Type
‘ =================================================================
‘ 2. メイン処理クラス(または標準モジュールでの実装イメージ)
‘ =================================================================
Sub Demonstration_Of_UDT_Dictionary()
Dim dic As Object
Set dic = CreateObject(“Scripting.Dictionary”)
‘ テストデータの作成
Dim key1 As TOrderKey, key2 As TOrderKey, searchKey As TOrderKey
key1.StoreCode = “TK01”
key1.CategoryNo = 10
key1.SalesDate = #4/1/2026#
key2.StoreCode = “TK01”
key2.CategoryNo = 2 ‘ カテゴリが違う
key2.SalesDate = #4/1/2026#
‘ データの登録(UDTをハッシュキーに変換して格納)
Dim hashKey1 As String, hashKey2 As String
hashKey1 = GetUDTHashKey(key1)
hashKey2 = GetUDTHashKey(key2)
dic.Add hashKey1, “東京本店 第10部門 売上データ”
dic.Add hashKey2, “東京本店 第2部門 売上データ”
‘ 検索テスト(key1と同じ値を持つ構造体を作成して検索)
searchKey.StoreCode = “TK01”
searchKey.CategoryNo = 10
searchKey.SalesDate = #4/1/2026#
Dim targetHash As String
targetHash = GetUDTHashKey(searchKey)
If dic.Exists(targetHash) Then
MsgBox “ヒットしました! 紐付く値: ” & dic(targetHash), vbInformation, “成功”
Else
MsgBox “データが存在しません。”, vbExclamation, “エラー”
End If
End Sub
‘ =================================================================
‘ 3. コアロジック:UDTから衝突のない一意な文字列キーを生成する関数
‘ =================================================================
Private Function GetUDTHashKey(ByRef udt As TOrderKey) As String
On Error GoTo ErrorHandler
‘ 【重要】フィールド間の境界があいまいにならないよう、
‘ エスケープされたデリミタ(例: “||”)と型情報を付与して結合する。
‘ 日付型はシリアル値(Double)に変換して精度を維持する。
Dim sb As String
Const DELIM As String = “::”
‘ 各要素を厳密に結合
sb = “SC:” & udt.StoreCode & DELIM & _
“CN:” & CStr(udt.CategoryNo) & DELIM & _
“DT:” & Format$(udt.SalesDate, “yyyy-mm-dd”)
‘ 必要に応じて、よりセキュアにしたい場合はここでSHA256等のハッシュ関数を通す。
‘ 今回はVBAのメモリ効率と速度を考慮し、一意性が担保された整形文字列をキーとする。
GetUDTHashKey = sb
Exit Function
ErrorHandler:
Err.Raise Err.Number, “GetUDTHashKey”, “UDTのハッシュ化に失敗しました: ” & Err.Description
End Function
—
建築的観点:なぜこの設計が「保守性・パフォーマンス」に優れているのか?
1. 暗黙の型変換エラーの排除
`CStr` や `Format$` を明示的に使用することで、VBAの悪名高い「Variantの勝手な解釈」を防いでいる。特に日付型は、ロケーション(PCの地域設定)によって文字列化の結果が揺らぐため、`Format$` でフォーマットを固定することが実務では絶対条件となる。
2. 可読性と拡張性の両立
もしUDTの項目が増えた場合でも、`GetUDTHashKey` 関数内の結合ロジックを修正するだけで、アプリケーション全体(Dictionaryを参照している全ての箇所)のキー生成仕様が一元管理される。呼び出し側を書き換える必要はない。
3. ファイル・DB連携への親和性
ここで生成される文字列キー(例: `SC:TK01::CN:10::DT:2026-04-01`)は、そのままCSVの複合キーや、SQLの `WHERE` 句のパラメータ(複合主キーの表現)として流用できる。VBA内部のメモリ上の都合だけでなく、外部リソースとのデータマッピング思想と完全に一致している。
—
チーフアーキテクトからの最終言
「動けばいい」というレベルのコードは、データ量が数千件を超えた瞬間、あるいは運用フェーズで仕様変更が入った瞬間に崩壊する。
Dictionaryのキーに複雑な構造を落とし込むときは、「一意性」「型の厳密性」「将来の拡張性」の3つを常にデザインの軸に置け。このハッシュ化戦略を取り入れた瞬間から、君の書くVBAコードは、おもちゃのマクロから「堅牢な業務システム」へと昇華する。
