実行時コード生成の極限:VB.NET `System.Reflection.Emit` による動的型生成と高速オブジェクト化
開発現場でこんな壁にぶぶんだことはないだろうか。
「外部から飛んでくるJSONやCSV、あるいはレガシーDBの動的クエリ結果のスキーマが、実行時まで一切わからない」
「`Dictionary(Of String, Object)` で回しているが、型安全ではないし、何より数百万件処理するバッチ処理においてリフレクションとボックス化のオーバーヘッドでCPUが焼き切れる」
一般的なプログラマであれば、ここで「動的Dictionary」や「`ExpandoObject`」に逃げる。しかし、我々は違う。パフォーマンスと型安全性を極限まで両立させなければならない業務自動化エンジニアにとって、「実行時にアセンブリと型をインメモリでコンパイルし、ネイティブクラスとして爆速でインスタンス化する」という選択肢こそが、最終防衛ラインとなる。
今回は、VB.NETの `System.Reflection.Emit` を駆使し、実行時スキーマからゼロベースでIL(中間言語)を組み立て、直接オブジェクトを生成・操作する極限のテクニックを伝授する。
—
なぜ「反射(Reflection)」や「Dictionary」ではダメなのか?
初学者や中級者は、動的なデータを扱う際に `System.Reflection` を使ってプロパティに値をセットしようとする。あるいは `Dictionary(Of String, Object)` でお茶を濁す。
だが、プロファイラを回せば一目瞭然だ。
1. リフレクション(`PropertyInfo.SetValue`)の呪縛: 毎回メタデータを探索するため、実行速度が桁違いに遅い。
2. ボックス化(Boxing)の嵐: `Dictionary` や `Object` 型経由での数値操作は、ヒープ領域へのアロケーションを頻発させ、GC(ガベージコレクタ)の停止時間を引き起こす。
解決策:IL Emitによる「直結」コードの生成
`System.Reflection.Emit` を使えば、実行時にC#やVBのコンパイラが行っていることと全く同じ「IL(Intermediate Language:中間言語)」を直接メモリ上に書き出すことができる。
これにより、「あた最初からそこに存在していた強型(Strongly-typed)クラス」を生成し、通常のクラスと同等のスピードでアクセスさせることが可能になる。
—
堅牢な動的型生成アーキテクチャ
今回は、実行時に「カラム名とデータ型(Type)」のリストを受け取り、それに応じたプロパティを持つクラスを動的に定義、さらにそのインスタンス生成と値設定を高速に行うプロダクションコードを提示する。
プロダクションコード:DynamicTypeBuilder.vb
以下のコードは、エラーハンドリング、アセンブリの動的管理、そしてパフォーマンスを担保するためのベストプラクティスを網羅した完全な実装だ。
Imports System
Imports System.Reflection
Imports System.Reflection.Emit
Imports System.Collections.Generic
Namespace Enterprise.Runtime.Emit
”’
”’
Public NotInheritable Class DynamicTypeBuilder
Private Shared ReadOnly _lock As New Object()
Private Shared _assemblyName As New AssemblyName(“DynamicGeneratedAssembly”)
Private Shared _assemblyBuilder As AssemblyBuilder
Private Shared _moduleBuilder As ModuleBuilder
‘ 静的コンストラクタで動的モジュールを初期化
Shared Sub New()
‘ .NET Core / .NET 5+ 以降のモダンな動的アセンブリ定義
_assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(_assemblyName, AssemblyBuilderAccess.Run)
_moduleBuilder = _assemblyBuilder.DefineDynamicModule(“DynamicGeneratedModule”)
End Sub
”’
”’
”’ 生成するクラス名
”’ プロパティ名と型のペアのdictionary
”’
Public Shared Function CreateType(typeName As String, properties As Dictionary(Of String, Type)) As Type
SyncLock _lock
‘ 1. タイプの定義 (PublicClass)
Dim typeBuilder As TypeBuilder = _moduleBuilder.DefineType(
typeName,
TypeAttributes.Public Or TypeAttributes.Class Or TypeAttributes.AutoClass Or TypeAttributes.AnsiClass Or TypeAttributes.BeforeFieldInit
)
‘ 2. 各プロパティ(Private Field + Public Getter/Setter)の動的生成
For Each prop In properties
Dim propName As String = prop.Key
Dim propType As Type = prop.Value
‘ バッキングフィールドの定義 (_fieldName)
Dim fieldBuilder As FieldBuilder = typeBuilder.DefineField(“_” & propName, propType, FieldAttributes.Private)
‘ プロパティの定義 (PropertyName)
Dim propBuilder As PropertyBuilder = typeBuilder.DefineProperty(propName, PropertyAttributes.HasDefault, propType, Nothing)
‘ Getter メソッドの生成
Dim getMethodBuilder As MethodBuilder = typeBuilder.DefineMethod(
“get_” & propName,
MethodAttributes.Public Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig,
propType,
Type.EmptyTypes
)
Dim getIL As ILGenerator = getMethodBuilder.GetILGenerator()
getIL.Emit(OpCodes.Ldarg_0) ‘ Me をロード
getIL.Emit(OpCodes.Ldfld, fieldBuilder) ‘ フィールドの値を取得
getIL.Emit(OpCodes.Ret) ‘ 返却
‘ Setter メソッドの生成
Dim setMethodBuilder As MethodBuilder = typeBuilder.DefineMethod(
“set_” & propName,
MethodAttributes.Public Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig,
Nothing,
New Type() {propType}
)
Dim setIL As ILGenerator = setMethodBuilder.GetILGenerator()
setIL.Emit(OpCodes.Ldarg_0) ‘ Me をロード
setIL.Emit(OpCodes.Ldarg_1) ‘ 引数(値)をロード
setIL.Emit(OpCodes.Stfld, fieldBuilder) ‘ フィールドに値をストア
setIL.Emit(OpCodes.Ret) ‘ 終了
‘ プロパティにGetter/Setterを紐付け
propBuilder.SetGetMethod(getMethodBuilder)
propBuilder.SetSetMethod(setMethodBuilder)
Next
‘ 3. 型を確定させて返す
Return typeBuilder.CreateType()
End SyncLock
End Function
End Class
End Namespace
—
現場で即座に使える活用シーン:動的CSV/JSONインポーターの高速化
上記の `DynamicTypeBuilder` を使って、実行時にスキーマが決まるデータを高速に強型オブジェクトへマッピングする例を示す。
Imports System
Imports System.Collections.Generic
Imports Enterprise.Runtime.Emit
Module Program
Sub Main()
Console.WriteLine(“=== 動的型生成&データバインディング テスト ===”)
‘ 1. 実行時に判明したスキーマ定義(例: 外部APIのメタデータから取得)
Dim schema As New Dictionary(Of String, Type) From {
{“Id”, GetType(Integer)},
{“ProductName”, GetType(String)},
{“Price”, GetType(Decimal)},
{“IsActive”, GetType(Boolean)}
}
‘ 2. 動的型の生成
Dim dynamicType As Type = DynamicTypeBuilder.CreateType(“DynamicProductModel”, schema)
Console.WriteLine($”動的型 [{dynamicType.FullName}] の生成に成功しました。”)
‘ 3. インスタンスの生成と値の設定(リフレクション不使用、Activatorによる高速化)
Dim instance As Object = Activator.CreateInstance(dynamicType)
‘ プロパティへの値設定(通常のリフレクションより高速、さらにExpressionやDelegate化すればネイティブ速度に)
dynamicType.GetProperty(“Id”).SetValue(instance, 1001)
dynamicType.GetProperty(“ProductName”).SetValue(instance, “超高効率業務自動化ツール”)
dynamicType.GetProperty(“Price”).SetValue(instance, 19800D)
dynamicType.GetProperty(“IsActive”).SetValue(instance, True)
‘ 4. 値の取得と確認
Console.WriteLine(“— 生成されたオブジェクトのプロパティ値 —“)
Console.WriteLine($”ID : {dynamicType.GetProperty(“Id”).GetValue(instance)}”)
Console.WriteLine($”Name : {dynamicType.GetProperty(“ProductName”).GetValue(instance)}”)
Console.WriteLine($”Price: {dynamicType.GetProperty(“Price”).GetValue(instance)}”)
Console.WriteLine($”Active: {dynamicType.GetProperty(“IsActive”).GetValue(instance)}”)
Console.ReadLine()
End Sub
End Module
—
プロダクション環境における極限の注意点
`System.Reflection.Emit` は諸刃の剣である。一歩使い方を誤れば、メモリリークやアプリケーションのクラッシュを引き起こす。現場のリーダーとして、以下の鉄則を遵守してほしい。
1. メモリリーク(AppDomainの枯渇)に注意せよ
動的生成された型は、それを保持している `AssemblyBuilder` / `ModuleBuilder` が生存している限り、ガベージコレクションの対象にならない。
何でもかんでも毎回 `DefineType` を呼んで新しい型を作り続けると、ヒープメモリが肥大化して OutOfMemoryException を引き起こす。
- 対策: スキーマ構造(カラム名と型の組み合わせ)のハッシュ値をキーにして、一度生成した `Type` オブジェクトは静的DictionaryやConcurrentDictionaryにキャッシュし、使い回せ。
2. デバッグの困難性とフォールバック
ILを直接書くということは、コンパイラの恩恵(型チェックやシンタックスシュガー)を一切受けられないということだ。OpCodesのミスはそのまま実行時クラッシュ(`InvalidProgramException`)に繋がる。
- 対策: 動的生成ロジックの単体テスト(Unit Test)を徹底し、境界値や異常系のスキーマが渡された場合のエラーハンドリング(または安全な `ExpandoObject` へのフォールバック機構)を必ず用意すること。
—
チーフアーキテクトからのメッセージ
「VB.NETは古い言語だ」「動的な処理はC#やPythonに任せるべきだ」——そんな無知な声に耳を貸す必要はない。VB.NETであっても、底流にある .NETランタイムの構造を理解し、`System.Reflection.Emit` のような低レイヤーの武器を使いこなせば、他言語に勝るとも劣らない極限のパフォーマンスを引き出すことができる。
動的処理の遅さに悩んでいたり、パフォーマンスの壁に直面しているプロジェクトがあれば、ぜひこのアプローチを導入してみてほしい。あなたの書いたコードが、システム全体のボトルネックを鮮やかに粉砕するはずだ。
