【テクニカル・上級編】VB.NETにおけるExpression(式ツリー)の動的構築:実行時にラムダ式を組み立てて高速なオブジェクトマッパーを作る – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

リフレクションの呪縛を解く:Expression Treeによる「超高速」オブジェクトマッピングの真髄

長年、VB6/VBAのレガシーコードと格闘し、.NETの進化を最前線で見届けてきた諸君へ。

「`Reflection`は遅い」。これは我々アーキテクトにとって、もはや呼吸と同じくらい自明の事実だ。しかし、システム連携においてDTO(Data Transfer Object)とエンティティの相互変換を繰り返す際、未だに泥臭いリフレクションでプロパティを舐め回している者がいれば、即刻その手を止めてほしい。

今回は、実行時にラムダ式を動的に構築し、コンパイル済みのデリゲートとしてキャッシュすることで、ネイティブコードに近い速度でオブジェクトマッピングを実現する「Expression Tree」の極意を伝授する。

なぜ、リフレクションは「罪」なのか

`System.Reflection`は強力だが、メタデータの取得と型チェックを呼び出しの度に行う。数万件のレコードを処理するバッチプログラムにおいて、このオーバーヘッドは致命的だ。

我々が目指すべきは、「書いたコードがコンパイルされるのと同じIL(中間言語)を、実行時に生成する」こと。これを可能にするのが `System.Linq.Expressions` である。

実践:高速オブジェクトマッパーの構築

以下のコードは、ソースオブジェクトからターゲットオブジェクトへ、名前の一致するプロパティを高速にコピーするマッパーの雛形だ。

Imports System.Linq.Expressions
Imports System.Reflection

Public Class FastMapper(Of TSource, TTarget)
‘ コンパイル済みのデリゲートをキャッシュ(ここが速度の源泉)
Private Shared ReadOnly _mapper As Func(Of TSource, TTarget)

Shared Sub New()
_mapper = CreateMapper()
End Sub

Private Shared Function CreateMapper() As Func(Of TSource, TTarget)
Dim sourceParam = Expression.Parameter(GetType(TSource), “src”)
Dim targetParam = Expression.Variable(GetType(TTarget), “dst”)

Dim bindings As New List(Of MemberBinding)()

‘ プロパティの突き合わせ
For Each targetProp In GetType(TTarget).GetProperties()
If Not targetProp.CanWrite Then Continue For

Dim sourceProp = GetType(TSource).GetProperty(targetProp.Name)
If sourceProp IsNot Nothing AndAlso sourceProp.PropertyType = targetProp.PropertyType Then
‘ dst.Property = src.Property の式を構築
bindings.Add(Expression.Bind(targetProp, Expression.Property(sourceParam, sourceProp)))
End If
Next

‘ New TTarget() { Property = src.Property } の初期化式を作成
Dim body = Expression.MemberInit(Expression.New(GetType(TTarget)), bindings)

‘ ラムダ式にコンパイル
Return Expression.Lambda(Of Func(Of TSource, TTarget))(body, sourceParam).Compile()
End Function

Public Shared Function Map(source As TSource) As TTarget
Return _mapper(source)
End Function
End Class

アーキテクトの視点:この実装の「深層」

このコードの真価は、`Shared Sub New` で実行時に一度だけコンパイルを行い、その結果をメモリ上に保持する点にある。

1. メモリとGCの制御

リフレクションで毎回 `PropertyInfo` を取得すると、不要なオブジェクトがヒープを汚染し、GC(ガベージコレクション)の頻度を高める。この手法では、静的なデリゲートがアプリケーションドメインの生存期間中キャッシュされるため、ヒープの断片化を極限まで抑制できる。

2. レガシー連携の極意:Windows APIとの共存

もしコピー対象に `IntPtr` を含む構造体や、Win32 APIとの受け渡しが必要なデータがある場合、単純な自動マッピングではメモリレイアウトが崩れる可能性がある。その際は、この式ツリー生成ロジックに `Marshal` クラスの指示を組み込むことで、「安全かつ最速なメモリコピー」を動的に定義することも可能だ。

3. 保守性への配慮

「動的なコード生成はブラックボックス化して保守が困難になる」と危惧する声がある。しかし、適切にインターフェースを切り出し、テストコードで生成されたデリゲートの挙動を担保してさえいれば、それは単なる「超高速なライブラリ」に過ぎない。

最後に:伝説のエンジニアとして告ぐ

技術とは、単に動くものを作ることではない。「制約という檻の中で、いかに物理限界に近づけるか」というゲームだ。

VB.NETは、その柔軟な構文と.NETランタイムの恩恵により、C#と同等、あるいはそれ以上のパフォーマンスを叩き出す能力を秘めている。リフレクションの海に溺れるのではなく、式ツリーを操り、コンパイラを手のひらで転がすエンジニアたれ。

次は、この式ツリーを拡張し、`DataTable`から型付きオブジェクトへの爆速変換モジュールを組んでみるといい。道は開かれている。

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